docs-merge-11

TowardsDataScience 2024 中文翻译(十二)

原文:TowardsDataScience

协议:CC BY-NC-SA 4.0

在多页面应用中实现 Streamlit-Authenticator

原文:towardsdatascience.com/implementing-streamlit-authenticator-across-multi-page-apps-5ad70ac315b3?source=collection_archive---------3-----------------------#2024-11-23

Streamlit-Authenticator 允许你在 Streamlit 应用中添加一种简单而强大的用户身份验证方法。

Brian RoepkeTowards Data Science Brian Roepke

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

--

AI 生成的图像由作者使用 HubSpot AI 制作

介绍

Streamlit 是一个广泛使用的工具,用于创建由数据支持的快速 Web 应用程序,但它缺乏的一个功能是管理多个用户并在他们使用应用时识别他们。

Streamlit 提供了一些基本的文档,介绍了如何通过在secrets.toml文件中使用硬编码的键值对来添加此功能,然而,这是一种非常简单的方法,你很可能会很快超越它!

幸运的是,开发者 Mohammad Khorasani 写了一个优秀的包,它以一种非常简单和优雅的方式添加了相当复杂的身份验证功能!这个包叫做Streamlit Authenticator,可以在 GitHub 上找到,并可以通过你喜欢的包管理器安装,比如 PIP。

Mohammad 提供了一个不错的示例应用和单页面应用的代码,但没有提供多页面应用的示例。我决定为有兴趣使用它的任何人提供一个起始点。我还确认了作者,以确保代码是正确的。

使用 Python 防护措施提高 LLM 输出可靠性

原文:towardsdatascience.com/improve-llm-output-reliability-with-python-guardrails-cc8163b26b0c?source=collection_archive---------2-----------------------#2024-05-31

利用验证函数防止你的 LLM 输出跌入深渊

Jan MajewskiTowards Data Science Jan Majewski

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

--

图片由作者生成

合适的 IF 语句可以让你的 LLM 输出更可靠

虽然 LLM 在创意和解决复杂任务方面表现出色,但它们往往难以遵循严格的规则,经常提供稍微超出设定边界的答案。在构建应用时,这个缺陷可能导致失败和荒谬的答案,从而让用户放弃使用。

好消息是,编写严格的规则是每个程序员的基本功,而且仅仅因为我们现在使用 LLM,并不意味着我们忘记了如何使用 if 语句。

如果某些逻辑可以通过几行代码来强制执行,你可能不需要将其输入到一个万亿参数的模型中

LLM 的最后一公里交付问题

控制输出在将 LLM 与应用的其他组件(如 API)集成时尤为重要。在开发一个text_to_params工具时,我需要将用户的公寓需求描述转化为搜索参数。我意识到,尽管生成的参数通常是正确的,但它们往往会…

使用改进的嵌入模型将你的 RAG 上下文召回率提升 95%

原文:towardsdatascience.com/improve-your-rag-context-recall-by-40-with-an-adapted-embedding-model-5d4a8f583f32?source=collection_archive---------0-----------------------#2024-10-12

附上逐步模型适配代码和结果

Vignesh BaskaranTowards Data Science Vignesh Baskaran

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

--

检索增强生成(RAG)是一种重要的技术,旨在将 LLM 融入业务场景,允许将专有知识注入 LLM。本文假设你已经具备 RAG 的相关知识,目的是提升你的 RAG 准确性。

让我们简要回顾一下这个过程。RAG 模型包括两个主要步骤:检索和生成。在检索步骤中,涉及多个子步骤,包括将上下文文本转换为向量、对上下文向量进行索引、根据用户查询检索上下文向量以及重新排序上下文向量。一旦查询的上下文被检索到,我们就进入生成阶段。在生成阶段,查询上下文与提示结合并发送给大语言模型(LLM)以生成回应。在发送到 LLM 之前,结合上下文的提示可能会经历缓存和路由步骤,以优化效率。

对于每个管道步骤,我们将进行大量实验,协同提高 RAG 的准确性。你可以参考下方的图片,其中列出了(但不限于)每个步骤中执行的实验。

改进的缓存技术使 Streamlit 仪表板性能提升了 5000 倍

原文:towardsdatascience.com/improved-caching-produces-a-5000x-performance-boost-on-streamlit-dashboards-4dd0353c4003?source=collection_archive---------4-----------------------#2024-01-15

PYTHON 编程

了解如何使用原生 Python 缓存来实现极速的仪表板。

Marcin KozakTowards Data Science Marcin Kozak

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

--

图片由 Myriam Jessier 提供,来源于 Unsplash

我几个月前开始使用它,必须说我真的很喜欢。之前我使用过 R Shiny 和 Python 的 dash 仪表板,但在编程简易性方面,Streamlit 比它们都要强。我发现它非常易于使用。虽然我从来不特别喜欢实现仪表板(我也不确定为什么),但 Streamlit 实际上改变了这一点;它变得很有趣!

然而,Streamlit 也有一些缺点。一个是 Streamlit 的代码往往看起来像是糟糕的 Python 代码,因为仪表板通常没有使用函数实现,而只是简单地列出一行行的代码。一些功能可以被移到函数或类中,但有时最好不要这么做。

Streamlit 的另一个缺点是其仪表板可能会很慢,原因有几个。首先,每当用户与仪表板进行交互时,Streamlit 都会重新运行整个仪表板脚本,这可能意味着需要重新运行例如计算、数据读取、图形和图表等操作。

在本文中,我们将讨论如何通过缓存加速 Streamlit 数据。我不会告诉你...

使用 Markdown 改进 RAG 文档处理

原文:towardsdatascience.com/improved-rag-document-processing-with-markdown-426a2e0dd82b?source=collection_archive---------0-----------------------#2024-11-19

如何阅读和转换 PDF 为 Markdown,以便在 LLMs 中获得更好的 RAG 结果

Dr. Leon EversbergTowards Data Science Dr. Leon Eversberg

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

--

图片来源:insung yoonUnsplash

Markdown 是一种轻量级、易读的语言,用于创建格式化文本。许多人可能熟悉 GitHub 上的 README.md 文件,它就是使用 Markdown 编写的。

以下是一些 Markdown 语法的基本示例:

# Heading level 1
## Heading level 2
### Heading level 3

This is **bold text**.

This is *italicized text*.

> This text is a quote

This is how to do a link [Link Text](https://www.example.org)

这段文本是代码


| Header 1   | Header 2   |
|------------|------------|
| table data | table data |

Markdown 似乎正在成为 大型语言模型(LLMs) 流行的格式。

Markdown 具有一些重要的优势,如 [1]:

  • 它为标题、表格、列表、链接等提供了结构

  • 它添加了 排版强调 元素,如粗体或斜体

  • 它是易于编写且具有可读性的

  • 它已经被广泛使用,例如在 GitHub 和 Jupyter notebooks 中

Markdown 不仅在 LLMs 的输入文档中有用,而且它也是像 ChatGPT 这样的聊天机器人格式化其内容的方式

使用机器学习提升商业表现

原文:towardsdatascience.com/improving-business-performance-with-machine-learning-fbdf5e280923?source=collection_archive---------3-----------------------#2024-06-13

无论你是数据科学家、分析师,还是业务分析师,你的目标都是交付能够提升商业表现的项目。

Juan Jose MunozTowards Data Science Juan Jose Munoz

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

--

图片来源:Daria Nepriakhina 🇺🇦Unsplash

可能会有诱惑专注于最新的机器学习发展或解决大问题。然而,通过使用简单的机器学习算法解决一些容易实现的低价值问题,通常也能带来很大的价值。

基准测试就是其中一个低价值问题。它是将业务 KPI 与类似组织进行对比的过程。它使企业能够从最优秀的企业中学习,并不断提高业绩。

基准测试有两种类型:

1. 内部:与公司内的单位/产品对比衡量 KPI

2. 外部:与竞争对手对比衡量 KPI

在我从事酒店行业的日常工作中,我们常常依赖于收集酒店数据的第三方公司来进行外部基准测试。然而,我们从他们那里获得的数据是有限的。另一方面,我们管理着超过 500 家酒店,拥有大量潜在的基准测试数据。

这是我们最近设定来解决的低价值问题。

无论你正在进行哪种类型的基准测试,第一步都是选择一组与目标酒店相似的酒店。在酒店行业,我们通常依赖于位置指标、品牌等级、房间数量、价格范围和市场需求。对于一两家酒店,我们通常是手动进行这样的操作,但对 500 家酒店进行手动操作是不可行的。

一旦确定了需要解决的问题,下一步就是选择使用的工具。机器学习提供了许多工具。然而,这个问题可以通过一个简单的算法族来解决:最近邻算法。

最近邻算法族

最近邻算法族是一种优化问题,旨在找到给定数据集中与指定点最接近或最相似的点。

这些算法在解决许多分类和回归问题方面非常成功。因此,Scikit Learn API 提供了一个出色的最近邻模块。

[## API 参考

这是 Scikit-learn 的类和函数参考。有关更多详细信息,请参考完整的用户指南,因为……

scikit-learn.org

选择合适的算法

大多数人都熟悉 K 最近邻(KNN);然而,Scikit Learn 提供了多种不同的最近邻算法,涵盖了监督学习和无监督学习任务。

对于我们的这个问题,我们没有任何标签。因此,我们需要寻找一个无监督算法。

如果你浏览 Scikit Learn 文档,你会找到 NearestNeighbors。这个算法执行无监督学习,用于实现邻居搜索。

这似乎涵盖了我们解决问题所需的内容。让我们开始准备数据并运行基准模型。

基准模型

1. 加载数据

酒店的表现通常取决于位置、品牌和规模。为了我们的分析,我们使用了两个数据集:

酒店数据:以下使用的酒店数据是基于原始数据集人工生成的,用于本次分析。

  • 品牌: 定义酒店的服务水平:豪华型、高档型、经济型

  • 房间数: 可供出售的房间数量

  • 市场: 酒店所在城市的名称

  • 国家: 国家名称

  • 纬度: 酒店的纬度位置

  • 经度: 酒店的经度位置

  • 机场代码: 最近国际机场的 3 字母代码

  • 市场层级: 定义市场的发展水平。

  • HCLASS: 表示酒店是城市酒店还是度假酒店

  • 需求: 表示酒店的年度入住率

  • 价格区间: 表示酒店的平均价格

我们还知道,酒店的表现可能会受到可达性的影响。为了衡量可达性,我们可以测量酒店距离主要国际机场的远近。机场数据来自世界银行:datacatalog.worldbank.org/search/dataset/0038117

  • Orig: 3 个字母的机场代码

  • 名称: 机场名称

  • 总座位数: 年度乘客量

  • 国家名称: 机场所在国家的名称

  • Airpot1Latitude: 机场的纬度

  • Airport1Longitude:机场经度

全球机场数据集根据知识共享署名 4.0 许可协议授权使用

让我们导入数据。

import pandas as pd
import numpy as np

data = pd.read_excel("mock_data.xlsx")
airport_data = pd.read_csv("airport_volume_airport_locations.csv")

酒店数据样本。图片由作者提供

机场数据样本。图片由作者提供

如前所述,酒店的表现高度依赖于位置。在我们的数据集中,我们有许多关于位置的度量,例如市场国家等。然而,这些定义通常较为宽泛,并不总是理想的。为了缩小相似位置的范围,我们需要创建一个可达性度量,定义为到最近国际机场的距离。

为了计算酒店到机场的距离,我们使用哈弗辛公式。哈弗辛公式用于计算球面上两点之间的距离,给定它们的纬度和经度。

# Below code is taken from geeksforgeeks
from math import radians, cos, sin, asin, sqrt

def distance_to_airport(lat, airport_lat, lon, airport_lon):

    #  Convert latitude and longitude values from decimal degrees to radians
    lon = radians(lon)
    airport_lon = radians(airport_lon)
    lat = radians(lat)
    airport_lat = radians(airport_lat)

    # Haversine formula
    dlon = airport_lon - lon
    dlat = airport_lat - lat
    a = sin(dlat / 2)**2 + cos(lat) * cos(airport_lat) * sin(dlon / 2)**2

    c = 2 * asin(sqrt(a))

    # Radius of earth in kilometers.
    r = 6371

    # return distance in KM
    return(c * r)

#Apply the distance_to_airport functions to each hotel
data["distance_to_airport"] = data.apply(lambda row: distance_to_airport(row["Latitude"],row["Airport1Latitude"],row["Longitude"],row["Airport1Longitude"]),axis=1)
data.head()

包含机场距离特征的结果数据框。图片由作者提供

下一步是移除我们不需要的任何列。

# Drop Columns that we dont need
# For the purpose of benchmarking we will keep the hotel feautures, and distance to airport
col_to_drop = ["Latitude","Longitude","Airport Code","Orig","Name","TotalSeats","Country Name","Airport1Latitude","Airport1Longitude"]

data_clean = data.drop(col_to_drop,axis=1)
data_clean.head()

接下来,我们对所有非数值变量进行编码,以便可以将它们输入到模型中。此时,重要的是要记住,我们需要原始标签来向团队呈现我们建议的分组,并便于验证。为此,我们将把编码信息存储在一个字典中。

from sklearn.preprocessing import LabelEncoder

# Create a LabelEncoder object for each object column
brand_encoder = LabelEncoder()
market_encoder = LabelEncoder()
country_encoder = LabelEncoder()
market_tier_encoder = LabelEncoder()
hclass_encoder = LabelEncoder()

# Fit each LabelEncoder on the unique values of the corresponding column
data_clean['BRAND'] = brand_encoder.fit_transform(data_clean['BRAND'])
data_clean['Market'] = market_encoder.fit_transform(data_clean['Market'])
data_clean['Country'] = country_encoder.fit_transform(data_clean['Country'])
data_clean['Market Tier'] = market_tier_encoder.fit_transform(data_clean['Market Tier'])
data_clean['HCLASS']= hclass_encoder.fit_transform(data_clean['HCLASS'])

# create a dictionnary with all the encoders for reverse encoding
encoders ={"BRAND" : brand_encoder,
           "Market": market_encoder,
           "Country": country_encoder,
           "Market Tier": market_tier_encoder,
           "HCLASS": hclass_encoder
}

data_clean.head()

编码数据。图片由作者提供

我们的数据现在是数值型的,但正如您所看到的,每列中的值的范围差异非常大。为了避免任何特征的范围不成比例地影响我们的模型,我们需要对数据进行重新缩放。

from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
data_scaled = scaler.fit_transform(data_clean)
data_scaled

标准化数据。图片由作者提供

此时,我们已准备好生成基准模型。

from sklearn.neighbors import NearestNeighbors

nns = NearestNeighbors()
nns.fit(data_scaled)
nns_results_model_0 = nns.kneighbors(data_scaled)[1]

nns_results_model_0

模型输出。图片由作者提供

模型的输出是一个索引列表,其中第一个索引是目标酒店,其他索引表示最靠近的酒店。

为了验证模型,我们可以通过直观检查结果。我们可以创建一个函数,该函数接收索引列表并解码值。

def clean_results(nns_results: np.ndarray,
                  encoders: dict,
                  data: pd.DataFrame):
  """
  Returns a dataframe with a list of labels for each Nearest Neighobor group
  """
  result = pd.DataFrame()

  # 1\. Get a list of Nearest Hotels based on our model
  for i in range(len(nns_results)):

    results = {} #empty dictionary to append each rows values

    # Each row in nns_results contains the indexs of the selected nearest neighbors
    # We use those index to get the Hotel names in our main data set
    results["Hotels"] = list(data.iloc[nns_results[i]].index)

    # 2\. Get the values for each features for all Nearest Neighbors groups
    for item in  data_clean.columns:
      results[item] = list(data.iloc[nns_results[i]][item])

    # 3\. Create a row for each Nearest Neighbor group and append to main DataFrame
    df = pd.DataFrame([results])
    result = pd.concat([result,df],axis=0)

  # 4\. Decode the labels to the encoded columns
  for key, val in encoders.items():
    result[key] = result[key].apply(lambda x : list(val.inverse_transform(x)))

  result.reset_index(drop=True,inplace=True) # Reset the index for clarity
  return result

results_model_0 = clean_results(nns_results=nns_results_model_0,
                                encoders=encoders,
                                data=data_clean)
results_model_0.head()

初步基准分组。图片由作者提供

因为我们使用的是无监督学习算法,所以没有广泛使用的准确度衡量标准。然而,我们可以使用领域知识来验证我们的分组。

通过直观检查这些分组,我们可以看到一些基准分组包含了经济型酒店和奢华型酒店的混合,这在商业上没有意义,因为酒店的需求本质上是不同的。

我们可以滚动查看数据并注意到一些差异,但我们能否提出自己的准确度衡量标准?

我们希望创建一个函数来衡量推荐的基准集在每个特征上的一致性。一种方法是通过计算每个集的每个特征的方差来实现。对于每个聚类,我们可以计算每个特征方差的平均值,然后可以将每个酒店聚类的方差取平均,得到一个总的模型得分。

根据我们的领域知识,我们知道,为了设置一个可比较的基准集,我们需要优先考虑相同品牌、可能相同市场和相同国家的酒店,如果使用不同的市场或国家,那么市场层级应该相同。

鉴于此,我们希望我们的度量能够对这些特征的方差给予更高的惩罚。为此,我们将使用加权平均来计算每个基准集的方差。我们还将分别打印关键特征和次要特征的方差。

总结一下,创建准确度量时,我们需要:

  1. 计算分类变量的方差:一种常见的方法是使用“基于熵的”度量,其中类别的多样性越高,熵(方差)越高。

  2. 计算数值变量的方差:我们可以计算标准差或范围(最大值与最小值之间的差异)。这衡量了每个聚类内数值数据的分布情况。

  3. 规范化数据:在应用权重之前,先规范化每个类别的方差得分,以确保没有单一特征因尺度差异而主导加权平均数。

  4. 为不同的度量应用权重:根据方差类型对其在聚类逻辑中的重要性进行加权。

  5. 计算加权平均数:计算每个聚类的这些方差得分的加权平均数。

  6. 聚合聚类的得分:总得分是所有聚类或行的加权方差得分的平均值。较低的平均得分表明我们的模型有效地将相似的酒店分组在一起,最小化了聚类内的方差。

from scipy.stats import entropy
from sklearn.preprocessing import MinMaxScaler
from collections import Counter

def categorical_variance(data):
    """
    Calculate entropy for a categorical variable from a list.
    A higher entropy value indicates datas with diverse classes.
    A lower entropy value indicates a more homogeneous subset of data.
    """
    # Count frequency of each unique value
    value_counts = Counter(data)
    total_count = sum(value_counts.values())
    probabilities = [count / total_count for count in value_counts.values()]
    return entropy(probabilities)

#set scoring weights giving higher weights to the most important features
scoring_weights = {"BRAND": 0.3,
           "Room_count": 0.025,
           "Market": 0.25,
           "Country": 0.15,
           "Market Tier": 0.15,
           "HCLASS": 0.05,
           "Demand": 0.025,
           "Price range": 0.025,
           "distance_to_airport": 0.025}

def calculate_weighted_variance(df, weights):
    """
    Calculate the weighted variance score for clusters in the dataset
    """
    # Initialize a DataFrame to store the variances
    variance_df = pd.DataFrame()

    # 1\. Calculate variances for numerical features
    numerical_features = ['Room_count', 'Demand', 'Price range', 'distance_to_airport']
    for feature in numerical_features:
        variance_df[f'{feature}'] = df[feature].apply(np.var)

    # 2\. Calculate entropy for categorical features
    categorical_features = ['BRAND', 'Market','Country','Market Tier','HCLASS']
    for feature in categorical_features:
        variance_df[f'{feature}'] = df[feature].apply(categorical_variance)

    # 3\. Normalize the variance and entropy values
    scaler = MinMaxScaler()
    normalized_variances = pd.DataFrame(scaler.fit_transform(variance_df),
                                        columns=variance_df.columns,
                                        index=variance_df.index)

    # 4\. Compute weighted average

    cat_weights = {f'{feature}': weights[f'{feature}'] for feature in categorical_features}
    num_weights = {f'{feature}': weights[f'{feature}'] for feature in numerical_features}

    cat_weighted_scores = normalized_variances[categorical_features].mul(cat_weights)
    df['cat_weighted_variance_score'] = cat_weighted_scores.sum(axis=1)

    num_weighted_scores = normalized_variances[numerical_features].mul(num_weights)
    df['num_weighted_variance_score'] = num_weighted_scores.sum(axis=1)

    return df['cat_weighted_variance_score'].mean(), df['num_weighted_variance_score'].mean()

为了保持代码简洁并跟踪我们的实验,让我们还定义一个函数来存储我们的实验结果。

# define a function to store the results of our experiments
def model_score(data: pd.DataFrame,
                weights: dict = scoring_weights,
                model_name: str ="model_0"):
  cat_score,num_score = calculate_weighted_variance(data,weights)
  results ={"Model": model_name,
            "Primary features score": cat_score,
            "Secondary features score": num_score}
  return results

model_0_score= model_score(results_model_0,scoring_weights)
model_0_score

基准模型结果。

现在我们已经有了一个基准,接下来看看我们是否能改进我们的模型。

通过实验改进我们的模型

到目前为止,当我们运行这段代码时,我们并不需要知道其背后的具体实现:

nns = NearestNeighbors()
nns.fit(data_scaled)
nns_results_model_0 = nns.kneighbors(data_scaled)[1]

为了改进我们的模型,我们需要理解模型参数,并了解如何与它们交互以获得更好的基准集。

让我们从查看 Scikit Learn 文档和源代码开始:

# the below is taken directly from scikit learn source

from sklearn.neighbors._base import KNeighborsMixin, NeighborsBase, RadiusNeighborsMixin

class NearestNeighbors_(KNeighborsMixin, RadiusNeighborsMixin, NeighborsBase):
    """Unsupervised learner for implementing neighbor searches.
    Parameters
    ----------
    n_neighbors : int, default=5
        Number of neighbors to use by default for :meth:`kneighbors` queries.

    radius : float, default=1.0
        Range of parameter space to use by default for :meth:`radius_neighbors`
        queries.

    algorithm : {'auto', 'ball_tree', 'kd_tree', 'brute'}, default='auto'
        Algorithm used to compute the nearest neighbors:

        - 'ball_tree' will use :class:`BallTree`
        - 'kd_tree' will use :class:`KDTree`
        - 'brute' will use a brute-force search.
        - 'auto' will attempt to decide the most appropriate algorithm
          based on the values passed to :meth:`fit` method.

        Note: fitting on sparse input will override the setting of
        this parameter, using brute force.

    leaf_size : int, default=30
        Leaf size passed to BallTree or KDTree.  This can affect the
        speed of the construction and query, as well as the memory
        required to store the tree.  The optimal value depends on the
        nature of the problem.

    metric : str or callable, default='minkowski'
        Metric to use for distance computation. Default is "minkowski", which
        results in the standard Euclidean distance when p = 2\. See the
        documentation of `scipy.spatial.distance
        <https://docs.scipy.org/doc/scipy/reference/spatial.distance.html>`_ and
        the metrics listed in
        :class:`~sklearn.metrics.pairwise.distance_metrics` for valid metric
        values.

    p : float (positive), default=2
        Parameter for the Minkowski metric from
        sklearn.metrics.pairwise.pairwise_distances. When p = 1, this is
        equivalent to using manhattan_distance (l1), and euclidean_distance
        (l2) for p = 2\. For arbitrary p, minkowski_distance (l_p) is used.

    metric_params : dict, default=None
        Additional keyword arguments for the metric function.
  """

    def __init__(
        self,
        *,
        n_neighbors=5,
        radius=1.0,
        algorithm="auto",
        leaf_size=30,
        metric="minkowski",
        p=2,
        metric_params=None,
        n_jobs=None,
    ):
        super().__init__(
            n_neighbors=n_neighbors,
            radius=radius,
            algorithm=algorithm,
            leaf_size=leaf_size,
            metric=metric,
            p=p,
            metric_params=metric_params,
            n_jobs=n_jobs,
        )

这里有很多事情在发生。

Nearestneighbor 类继承自 NeighborsBase,后者是最近邻估计器的基类。此类处理最近邻搜索所需的常见功能,如:

  • n_neighbors(使用的邻居数量)

  • radius(基于半径的邻居搜索的半径)

  • algorithm(用于计算最近邻的算法,如 ‘ball_tree’,‘kd_tree’ 或 ‘brute’)

  • metric(用于计算的距离度量)

  • metric_params(距离函数的额外关键字参数)

Nearestneighbor 类还继承自 KNeighborsMixinRadiusNeighborsMixin 类。这些 Mixin 类为 Nearestneighbor 添加了特定的邻居搜索功能。

  • KNeighborsMixin 提供了一个功能,用于查找离某一点最近的固定数量 k 个邻居。它通过计算邻居之间的距离及其索引,并基于每个点的 k 最近邻来构建一个点之间的连接图。

  • RadiusNeighborsMixin 基于半径邻居算法,寻找给定半径内的所有邻居。这种方法在关注捕捉所有位于有意义距离阈值内的点时非常有用,而不是固定数量的点。

根据我们的场景,KNeighborsMixin 提供了我们所需的功能。

在我们能改进模型之前,需要理解一个关键参数,那就是距离度量。

简短介绍距离

文档提到,NearestNeighbor 算法默认使用 “Minkowski” 距离,并为我们提供了 SciPy API 的参考。

scipy.spatial.distance 中,我们可以看到“Minkowski” 距离的两种数学表示:

∥u−v∥ p​=( i ∑​∣u i​−v i​∣ p ) 1/p

这个公式计算的是所有元素差异的幂和的 p 次根。

“Minkowski” 距离的第二种数学表示是:

∥u−v∥ p​=( i ∑​w i​(∣u i​−v i​∣ p )) 1/p

这个公式与第一个类似,但它引入了权重 wi,使得某些维度的差异被强调或减弱。在某些特征比其他特征更为重要的情况下,这非常有用。默认情况下,该设置为 None,这意味着所有特征的权重均为 1.0。

这是改进我们模型的一个很好的选项,因为它允许我们将领域知识传递给模型,并强调对用户最相关的相似性。

如果我们查看公式,会看到参数 p。该参数影响算法计算距离时采取的“路径”。默认情况下,p=2,这代表欧几里得距离。

你可以将欧几里得距离理解为通过在两个点之间画一条直线来计算距离。这通常是最短的距离,但在高维空间中,这并不总是计算距离的最理想方式。如需了解为什么是这样,可以参考这篇很棒的在线论文:bib.dbvis.de/uploadedFiles/155.pdf

另一个常见的 p 值是 1. 这代表曼哈顿距离。 可以将其理解为在类似网格的路径上测量两个点之间的距离。

另一方面,如果我们将 p 增加到无穷大,我们将得到切比雪夫距离,它被定义为向量中任何对应元素之间的最大绝对差异。它本质上衡量的是最坏情况下的差异,因此在你希望确保没有单一特征变化过大的情况下非常有用。

通过阅读并熟悉文档,我们发现了一些可以改善模型的可能选项。

实验 1:基准模型,n_neighbors = 4

默认情况下,n_neighbors 为 5,但对于我们的基准集,我们希望将每个酒店与 3 个最相似的酒店进行比较。为此,我们需要将 n_neighbors 设置为 4(目标酒店 + 3 个同类酒店)。

nns_1= NearestNeighbors(n_neighbors=4)
nns_1.fit(data_scaled)
nns_1_results_model_1 = nns_1.kneighbors(data_scaled)[1]
results_model_1 = clean_results(nns_results=nns_1_results_model_1,
                                encoders=encoders,
                                data=data_clean)
model_1_score= model_score(results_model_1,scoring_weights,model_name="baseline_k_4")
model_1_score

我们的主要特征有所改善。图片由作者提供

实验 2:添加权重

根据文档,我们可以通过传递权重来强调某些特征之间的关系。根据我们的领域知识,我们已经确定了需要强调的特征,在这种情况下是品牌、市场、国家和市场层级。

# set up weights for distance calculation
weights_dict =  {"BRAND": 5,
           "Room_count": 2,
           "Market": 4,
           "Country": 3,
           "Market Tier": 3,
           "HCLASS": 1.5,
           "Demand": 1,
           "Price range": 1,
           "distance_to_airport": 1}
# Transform the wieghts dictionnary into a list by keeping the scaled data column order
weights = [ weights_dict[idx] for idx in list(scaler.get_feature_names_out())]

nns_2= NearestNeighbors(n_neighbors=4,metric_params={ 'w': weights})
nns_2.fit(data_scaled)
nns_2_results_model_2 = nns_2.kneighbors(data_scaled)[1]
results_model_2 = clean_results(nns_results=nns_2_results_model_2,
                                encoders=encoders,
                                data=data_clean)
model_2_score= model_score(results_model_2,scoring_weights,model_name="baseline_with_weights")
model_2_score

主要特征得分持续提高。图片由作者提供

通过权重将领域知识传递给模型显著提高了得分。接下来,让我们测试距离度量的影响。

实验 3:使用曼哈顿距离

到目前为止,我们一直在使用欧几里得距离。让我们看看如果改用曼哈顿距离会发生什么。

nns_3= NearestNeighbors(n_neighbors=4,p=1,metric_params={ 'w': weights})
nns_3.fit(data_scaled)
nns_3_results_model_3 = nns_3.kneighbors(data_scaled)[1]
results_model_3 = clean_results(nns_results=nns_3_results_model_3,
                                encoders=encoders,
                                data=data_clean)
model_3_score= model_score(results_model_3,scoring_weights,model_name="Manhattan_with_weights")
model_3_score

主要得分显著下降。图片由作者提供

实验 4:使用切比雪夫距离

将 p 降到 1 后,取得了一些良好的改进。让我们看看当 p 接近无穷大时会发生什么。

要使用切比雪夫距离,我们将把度量参数更改为 Chebyshev。默认的 sklearn 切比雪夫度量没有权重参数。为了解决这个问题,我们将定义一个自定义的 weighted_chebyshev 度量。

#  Define the custom weighted Chebyshev distance function
def weighted_chebyshev(u, v, w):
    """Calculate the weighted Chebyshev distance between two points."""
    return np.max(w * np.abs(u - v))

nns_4 = NearestNeighbors(n_neighbors=4,metric=weighted_chebyshev,metric_params={ 'w': weights})
nns_4.fit(data_scaled)
nns_4_results_model_4 = nns_4.kneighbors(data_scaled)[1]
results_model_4 = clean_results(nns_results=nns_4_results_model_4,
                                encoders=encoders,
                                data=data_clean)
model_4_score= model_score(results_model_4,scoring_weights,model_name="Chebyshev_with_weights")
model_4_score

比基准模型要好,但比之前的实验高。图片由作者提供

通过实验,我们成功地降低了主要特征的方差得分。

让我们可视化结果。

results_df = pd.DataFrame([model_0_score,model_1_score,model_2_score,model_3_score,model_4_score]).set_index("Model")
results_df.plot(kind='barh')

实验结果。图片由作者提供

根据我们的需求,使用带权重的曼哈顿距离似乎能提供最准确的基准集。

在实现基准集之前的最后一步是检查得分最高的主要特征集,并确定需要采取哪些步骤。

# Histogram of Primary features score
results_model_3["cat_weighted_variance_score"].plot(kind="hist")

得分分布。图片由作者提供

exceptions = results_model_3[results_model_3["cat_weighted_variance_score"]>=0.4]

print(f" There are {exceptions.shape[0]} benchmark sets with significant variance across the primary features")

图片由作者提供

这 18 个案例需要复审,以确保基准集是相关的。

正如你所见,凭借几行代码和对最近邻搜索的一些理解,我们成功地设置了内部基准集。我们现在可以分发这些基准集,并开始衡量酒店的 KPI 与其基准集的对比。

你不一定总是需要专注于最前沿的机器学习方法来提供价值。很多时候,简单的机器学习也能带来巨大价值。

在你的业务中,有哪些容易通过机器学习解决的低挂果?

参考文献

世界银行. “世界发展指标.” 访问日期:2024 年 6 月 11 日,来源:datacatalog.worldbank.org/search/dataset/0038117

Aggarwal, C. C., Hinneburg, A., & Keim, D. A. (n.d.). 高维空间中距离度量的惊人行为. IBM T. J. Watson 研究中心和哈雷大学计算机科学研究所. 来源:bib.dbvis.de/uploadedFiles/155.pdf

SciPy v1.10.1 手册. scipy.spatial.distance.minkowski. 访问日期:2024 年 6 月 11 日,来源:docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.minkowski.html

GeeksforGeeks. 哈弗辛公式计算球面上两点之间的距离. 访问日期:2024 年 6 月 11 日,来源:www.geeksforgeeks.org/haversine-formula-to-find-distance-between-two-points-on-a-sphere/

scikit-learn. 邻居模块. 访问日期:2024 年 6 月 11 日,来源:scikit-learn.org/stable/modules/classes.html#module-sklearn.neighbors

通过少量示例以无训练方式提升 CLIP 性能

原文:towardsdatascience.com/improving-clip-performance-in-training-free-manner-with-few-shot-examples-a59f6b29cdc8?source=collection_archive---------16-----------------------#2024-01-30

第三部分 — 使用 Tip-Adapter 对零样本分类的简单扩展

Alexey KravetsTowards Data Science Alexey Kravets

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

--

这是关于如何提升 CLIP 分类性能的第三篇文章。你可以在这里找到第一篇,这里找到第二篇。在前两篇文章中,我们的重点是零样本分类,我们发现利用大型语言模型(LLM)定制提示可以提高 CLIP 的零样本分类性能。在本文中,我们将探索如何在为每个类别提供少量视觉示例的情况下,进一步提升 CLIP 的分类性能。在继续之前,我建议你先回顾一下我系列中的第一篇文章,以加深对 CLIP 的理解。

引言

CLIP 的零样本分类能力受到其在预训练期间获得的知识的限制。因此,如果我们想对在 CLIP 预训练数据中稀缺或不存在的数据进行分类,分类性能可能会不尽如人意。尽管构建一个广泛的数据集可能具有挑战性,但通常可以为每个类别获取少量示例。提升 CLIP 性能的一种方法是,在其上方加上小型适配器,并使用少量示例图像进行训练,同时保持 CLIP 的原始权重冻结。然而,在某些情况下,训练即使是小型适配器也可能不可行。作为替代,我们可以以无训练的方式利用 CLIP,同时仍然…

使用 Polars 提高数据转换过程中的代码质量

原文:towardsdatascience.com/improving-code-quality-during-data-transformation-with-polars-92997e67c8a9?source=collection_archive---------10-----------------------#2024-08-09

Nikolai PotapovTowards Data Science Nikolai Potapov

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

--

由 Dall-E AI 生成的图片

在我们作为数据/分析工程师的日常工作中,编写 ETL/ELT 工作流和管道(或者你所在的公司使用其他术语)是我们工作中常规且重要的一部分。然而,在这篇文章中,我将只关注转换阶段。为什么?因为在这一阶段,来自不同来源和不同类型的数据获得了对公司有意义的商业价值。这个阶段非常重要,而且极其微妙,因为一个错误可能会瞬间误导用户,导致他们失去对数据的信任。

为了说明提高代码质量的过程,让我们考虑一个假设的例子。假设有一个网站,我们记录用户的行为,例如他们查看了什么和购买了什么。我们将使用user_id表示用户 ID,product_id表示产品,action_type表示行为类型(可以是查看或购买),action_dt表示行为的时间戳。

from dataclasses import dataclass
from datetime import datetime, timedelta
from random import choice, gauss, randrange, seed
from typing import Any, Dict

import polars as pl

seed(42)
base_time= datetime(2024, 8, 9, 0, 0, 0, 0)

user_actions_data = [
    {
        "user_id": randrange(10),
        "product_id": choice(["0001", "0002", "0003"]),
        "action_type": ("purchase" if gauss() > 0.6 else "view"),
        "action_dt": base_time - timedelta(minutes=randrange(100_000)),
    }
    for x in range(100_000)
]

user_actions_df = pl.DataFrame(user_actions_data)

此外,对于我们的任务,我们还需要一个产品目录,在我们的例子中,它只包含product_id和价格(price)。我们的数据现在已经准备好用于示例。

product_catalog_data = {"product_id": ["0001", "0002", "0003"], "price": [10, 30, 70]}
product_catalog_df = pl.DataFrame(product_catalog_data)

现在,让我们处理第一个任务:创建一个报告,其中包含每个用户在前一天的总购买金额以及购买商品数量与查看商品数量的比例。这个任务并不复杂,可以快速实现。以下是使用 Polars 的实现方式:

yesterday = base_time - timedelta(days=1)
result = (
    user_actions_df.filter(pl.col("action_dt").dt.date() == yesterday.date())
    .join(product_catalog_df, on="product_id")
    .group_by(pl.col("user_id"))
    .agg(
        [
            (
                pl.col("price")
                .filter(pl.col("action_type") == "purchase")
                .sum()
            ).alias("total_purchase_amount"),
            (
                pl.col("product_id").filter(pl.col("action_type") == "purchase").len()
                / pl.col("product_id").filter(pl.col("action_type") == "view").len()
            ).alias("purchase_to_view_ratio"),
        ]
    )
    .sort("user_id")
)

这是一种可以部署到生产环境的工作解决方案,有人可能会这么说,但我们不同,因为你已经打开了这篇文章。一开始,我强调过,我将特别关注转换步骤。

如果我们考虑到这段代码的长期维护、测试,并且记住将会有数百个这样的报告,我们必须认识到,每一个后续开发人员对这段代码的理解都将低于前一个开发人员,从而增加每次修改时出现错误的几率。

我想减少这个风险,因此我采用了以下方法:

步骤 1:让我们将所有的业务逻辑拆分到一个独立的类中,例如DailyUserPurchaseReport

@dataclass
class DailyUserPurchaseReport:

步骤 2:让我们定义这个类应该接受的参数:sources - 我们工作所需的各种来源,和params - 可能会变化的可变参数,在我们的例子中,这可能是报告日期。

@dataclass
class DailyUserPurchaseReport:

    sources: Dict[str, pl.LazyFrame]
    params: Dict[str, Any]

步骤 3:定义一个方法来执行转换,例如,execute

@dataclass
class DailyUserPurchaseReport:

    sources: Dict[str, pl.LazyFrame]
    params: Dict[str, Any]

    def execute(self) -> pl.DataFrame:
        pass

步骤 4:将整个过程拆分成独立的函数,每个函数都接受一个pl.LazyFrame并返回一个pl.LazyFrame

@dataclass
class DailyUserPurchaseReport:

    sources: Dict[str, pl.LazyFrame]
    params: Dict[str, Any]

    def _filter_actions_by_date(self, frame: pl.LazyFrame) -> pl.LazyFrame:
        pass

    def _enrich_user_actions_from_product_catalog(self, frame: pl.LazyFrame) -> pl.LazyFrame:
        pass

    def _calculate_key_metrics(self, frame: pl.LazyFrame) -> pl.LazyFrame:
        pass

    def execute(self) -> pl.DataFrame:
        pass

步骤 5:现在,使用魔法函数pipe将我们的整个管道连接在一起。这正是我们在各处使用pl.LazyFrame的原因:

 def execute(self) -> pl.DataFrame:
        result: pl.DataFrame = (
            self.sources["user_actions"]
            .pipe(self._filter_actions_by_date)
            .pipe(self._enrich_user_actions_from_product_catalog)
            .pipe(self._calculate_key_metrics)
            .collect()
        )
        return result

建议在管道操作时使用 LazyFrame,以充分利用查询优化和并行化。

最终代码:

@dataclass
class DailyUserPurchaseReport:
    """
    Generates a report containing the total purchase amount and the ratio of purchased items
    to viewed items from the previous day for each user.

    Attributes:
        sources (Dict[str, pl.LazyFrame]): A dictionary containing the data sources, including:
            - 'user_actions': A LazyFrame containing user actions data.
            - 'product_catalog': A LazyFrame containing product catalog data.
        params (Dict[str, Any]): A dictionary containing parameters, including:
            - 'report_date': The date for which the report should be generated (previous day).
    """

    sources: Dict[str, pl.LazyFrame]
    params: Dict[str, Any]

    def _filter_actions_by_date(self, frame: pl.LazyFrame) -> pl.LazyFrame:
        """
         Filters user actions data to include only records from the specified date.

        Args:
            frame (pl.LazyFrame): A LazyFrame containing user actions data.

        Returns:
            pl.LazyFrame: A LazyFrame containing user actions data filtered by the specified date.
        """
        return frame.filter(pl.col("action_dt").dt.date() == self.params["report_date"])

    def _enrich_user_actions_from_product_catalog(
        self, frame: pl.LazyFrame
    ) -> pl.LazyFrame:
        """
        Joins the user actions data with the product catalog to include product prices.

        Args:
            frame (pl.LazyFrame): A LazyFrame containing user actions data.

        Returns:
            pl.LazyFrame: A LazyFrame containing user actions data enriched with product prices.
        """
        return frame.join(self.sources["product_catalog"], on="product_id")

    def _calculate_key_metrics(self, frame: pl.LazyFrame) -> pl.LazyFrame:
        """
        Calculates the total purchase amount and the ratio of purchased items to viewed items.

        Args:
            frame (pl.LazyFrame): A LazyFrame containing enriched user actions data.

        Returns:
            pl.LazyFrame: A LazyFrame containing the total purchase amount and purchase-to-view ratio for each user.

        """
        return (
            frame.group_by(pl.col("user_id"))
            .agg(
                [
                    (
                        pl.col("price")
                        .filter(pl.col("action_type") == "purchase")
                        .sum()
                    ).alias("total_purchase_amount"),
                    (
                        pl.col("product_id")
                        .filter(pl.col("action_type") == "purchase")
                        .len()
                        / pl.col("product_id").filter(pl.col("action_type") == "view").len()
                    ).alias("purchase_to_view_ratio"),
                ]
            )
            .sort("user_id")
        )

    def execute(self) -> pl.DataFrame:
        """
        Executes the report generation process.

        This method performs the following steps:
            1\. Filters user actions data to include only records from the previous day.
            2\. Joins the filtered user actions data with the product catalog.
            3\. Calculates the total purchase amount and purchase-to-view ratio for each user.
            4\. Returns the final report as a DataFrame.

        Returns:
            pl.DataFrame: A DataFrame containing the total purchase amount and purchase-to-view ratio for each user.
        """
        result: pl.DataFrame = (
            self.sources["user_actions"]
            .pipe(self._filter_actions_by_date)
            .pipe(self._enrich_user_actions_from_product_catalog)
            .pipe(self._calculate_key_metrics)
            .collect()
        )
        return result

让我们检查一下执行情况:

# prepare sources
user_actions: pl.LazyFrame = user_actions_df.lazy()
product_catalog: pl.LazyFrame = product_catalog_df.lazy()

# get report date
yesterday: datetime = base_time - timedelta(days=1)

# report calculation
df: pl.DataFrame = DailyUserPurchaseReport(
    sources={"user_actions": user_actions, "product_catalog": product_catalog},
    params={"report_date": yesterday},
).execute()

结果:

┌─────────┬───────────────────────┬────────────────────────┐
│ user_id ┆ total_purchase_amount ┆ purchase_to_view_ratio │
│ ---     ┆ ---                   ┆ ---                    │
│ i64     ┆ i64                   ┆ f64                    │
╞═════════╪═══════════════════════╪════════════════════════╡
│ 0       ┆ 1880                  ┆ 0.422018               │
│ 1       ┆ 1040                  ┆ 0.299065               │
│ 2       ┆ 2220                  ┆ 0.541667               │
│ 3       ┆ 1480                  ┆ 0.436782               │
│ 4       ┆ 1240                  ┆ 0.264463               │
│ 5       ┆ 930                   ┆ 0.254717               │
│ 6       ┆ 1080                  ┆ 0.306122               │
│ 7       ┆ 1510                  ┆ 0.345133               │
│ 8       ┆ 2050                  ┆ 0.536842               │
│ 9       ┆ 1320                  ┆ 0.414414               │
└─────────┴───────────────────────┴────────────────────────┘

奖励

对于使用测试驱动开发(TDD)的人来说,这种方法尤为有益。TDD 强调在实际实现之前编写测试。通过定义清晰的小函数,你可以为每个转换过程编写精确的测试,确保每个函数按预期行为运行。这不仅使过程更加顺畅,还确保了你的转换在每个步骤都经过充分验证。

结论

在本文中,我概述了一种使用 Polars 改善数据工作流代码质量的结构化方法。通过将转换步骤隔离并将过程拆分为独立的、可管理的部分,我们确保了我们的代码既稳健又易于维护。通过使用pl.LazyFramepipe函数,我们充分利用了 Polars 在查询优化和并行化方面的能力。这种方法不仅提高了数据转换的效率,还确保了我们处理的数据的完整性和业务相关性。通过遵循这些步骤,你可以创建更可靠、可扩展的数据工作流,最终推动更好的数据驱动决策。

分享您的经验

如果你有经验或有用的技巧,欢迎在评论中分享你的意见。了解其他开发者的经验总是很有趣的。

提升代码质量:数组和 DataFrame 类型提示

原文:towardsdatascience.com/improving-code-quality-with-array-and-dataframe-type-hints-cac0fb75cc11?source=collection_archive---------5-----------------------#2024-09-19

泛型类型规范如何实现强大的静态分析和运行时验证

Christopher ArizaTowards Data Science Christopher Ariza

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

--

作者照片

随着 Python 类型注解(或类型提示)工具的发展,越来越复杂的数据结构可以被指定类型,从而提高了代码的可维护性和静态分析能力。数组和 DataFrame 作为复杂的数据容器,直到最近才开始在 Python 中支持完整的类型注解。NumPy 1.22 引入了数组和数据类型(dtypes)的通用规格。在 NumPy 的基础上,StaticFrame 2.0 引入了 DataFrame 的完整类型规格,采用了 NumPy 原语和可变泛型。本文展示了对数组和 DataFrame 进行完全类型提示的实际方法,并展示了如何通过静态分析和运行时验证来提高代码质量。

StaticFrame是一个开源的 DataFrame 库,我是该库的作者之一。

类型提示提升代码质量

类型提示(参见PEP 484)通过多种方式提升代码质量。与使用变量名或注释来传达类型不同,基于 Python 对象的类型注解提供了可维护且富有表现力的工具用于类型规范。这些类型注解可以通过类型检查工具(如mypypyright)进行测试,能够在不执行代码的情况下迅速发现潜在的 bug。

相同的注解可以用于运行时验证。虽然在 Python 中依赖鸭子类型(duck-typing)而非运行时验证较为常见,但对于复杂数据结构(如数组和 DataFrame),往往更需要运行时验证。例如,期望 DataFrame 作为参数的接口,如果传入一个 Series,可能不需要显式验证,因为错误类型的使用很可能会引发异常。然而,期望二维浮点数组的接口,如果传入一个布尔值数组,则可能受益于验证,因为错误类型的使用可能不会引发异常。

许多重要的类型工具仅在最新版本的 Python 中可用。幸运的是,typing-extensions包为较旧版本的 Python 提供了标准库工具的后向移植。一个相关的挑战是,类型检查器可能需要一些时间才能完全支持新特性:这里展示的许多示例至少需要mypy 1.9.0。

基本类型注解

如果没有类型注解,Python 函数签名无法指示期望的类型。例如,下面的函数可能接受和返回任何类型:

def process0(v, q): ... # no type information

通过添加类型注解,函数签名向读者表明期望的类型。在现代 Python 中,可以使用用户定义的类和内置类来指定类型,更多的资源(如AnyIteratorcast()Annotated)可以在标准库typing模块中找到。例如,下面的接口通过明确指定期望类型来改进上述接口:

def process0(v: int, q: bool) -> list[float]: ...

当与像mypy这样的类型检查器一起使用时,违反类型注解规范的代码将在静态分析时抛出错误(如下所示的评论)。例如,当需要布尔值时提供整数是一个错误:

x = process0(v=5, q=20)
# tp.py: error: Argument "q" to "process0"
# has incompatible type "int"; expected "bool"  [arg-type]

静态分析只能验证静态定义的类型。运行时输入和输出的完整范围通常更加多样化,这意味着需要某种形式的运行时验证。通过重复使用类型注解进行运行时验证,可以实现两全其美。虽然有一些库可以做到这一点(例如,typeguardbeartype),但 StaticFrame 提供了专门用于全面数组和 DataFrame 类型注解验证的工具CallGuard

Python 装饰器非常适合利用注解进行运行时验证。CallGuard提供了两个装饰器:[@CallGuard](http://twitter.com/CallGuard).check,在出错时抛出详细的Exception,或者[@CallGuard](http://twitter.com/CallGuard).warn,在出错时发出警告。

通过在上面的process0函数中扩展[@CallGuard](http://twitter.com/CallGuard).check,可以使用相同的类型注解,当运行时对象违反类型注解的要求时,抛出Exception(如评论中再次显示):

import static_frame as sf

@sf.CallGuard.check
def process0(v: int, q: bool) -> list[float]:
    return [x * (0.5 if q else 0.25) for x in range(v)]

z = process0(v=5, q=20)
# static_frame.core.type_clinic.ClinicError:
# In args of (v: int, q: bool) -> list[float]
# └── Expected bool, provided int invalid

虽然类型注解必须是有效的 Python,但它们在运行时是无关的,且可能是错误的:有可能拥有正确验证的类型,但它们并未反映运行时的实际情况。如上所示,通过重用类型注解进行运行时检查可以确保注解的有效性。

数组类型注解

允许组件类型规范的 Python 类被称为“泛型”。组件类型通过位置型“类型变量”来指定。例如,一个整数列表的注解是list[int];一个由整数和字符串元组作为键的浮点数字典的注解是dict[tuple[int, str], float]

在 NumPy 1.20 版本中,ndarraydtype 变得更加泛型化。泛型 ndarray 需要两个参数,一个是形状,另一个是 dtype。由于第一个参数的使用仍在开发中,因此通常使用 Any。第二个参数 dtype 本身是一个泛型,要求使用一个类型变量来指定 NumPy 类型,例如 np.int64。NumPy 还提供了更为通用的泛型类型,如 np.integer[Any]

例如,一个布尔数组的注解是np.ndarray[Any, np.dtype[np.bool_]];任何类型整数的数组的注解是np.ndarray[Any, np.dtype[np.integer[Any]]]

由于带有组件类型规范的泛型注解可能变得冗长,因此将它们存储为类型别名(此处以“T”作为前缀)是实际的做法。以下函数指定了此类别名,并在函数中使用它们。

from typing import Any
import numpy as np

TNDArrayInt8 = np.ndarray[Any, np.dtype[np.int8]]
TNDArrayBool = np.ndarray[Any, np.dtype[np.bool_]]
TNDArrayFloat64 = np.ndarray[Any, np.dtype[np.float64]]

def process1(
        v: TNDArrayInt8,
        q: TNDArrayBool,
        ) -> TNDArrayFloat64:
    s: TNDArrayFloat64 = np.where(q, 0.5, 0.25)
    return v * s

如前所述,当与 mypy 一起使用时,违反类型注解的代码将在静态分析期间引发错误。例如,在需要布尔值时提供整数是一个错误:

v1: TNDArrayInt8 = np.arange(20, dtype=np.int8)
x = process1(v1, v1)
# tp.py: error: Argument 2 to "process1" has incompatible type
# "ndarray[Any, dtype[floating[_64Bit]]]"; expected "ndarray[Any, dtype[bool_]]"  [arg-type]

该接口要求使用 8 位有符号整数(np.int8);尝试使用不同大小的整数也是错误的:

TNDArrayInt64 = np.ndarray[Any, np.dtype[np.int64]]
v2: TNDArrayInt64 = np.arange(20, dtype=np.int64)
q: TNDArrayBool = np.arange(20) % 3 == 0
x = process1(v2, q)
# tp.py: error: Argument 1 to "process1" has incompatible type
# "ndarray[Any, dtype[signedinteger[_64Bit]]]"; expected "ndarray[Any, dtype[signedinteger[_8Bit]]]"  [arg-type]

尽管某些接口可能受益于如此狭窄的数值类型规范,但使用 NumPy 的泛型类型,如np.integer[Any]np.signedinteger[Any]np.float[Any]等,依然可以进行更广泛的规范。例如,我们可以定义一个新的函数,接受任何大小的有符号整数。静态分析现在通过了 TNDArrayInt8TNDArrayInt64 数组。

TNDArrayIntAny = np.ndarray[Any, np.dtype[np.signedinteger[Any]]]
def process2(
        v: TNDArrayIntAny, # a more flexible interface
        q: TNDArrayBool,
        ) -> TNDArrayFloat64:
    s: TNDArrayFloat64 = np.where(q, 0.5, 0.25)
    return v * s

x = process2(v1, q) # no mypy error
x = process2(v2, q) # no mypy error

正如上面所示的元素,使用泛型指定的 NumPy 数组可以在运行时进行验证,前提是它们使用了 CallGuard.check 装饰器:

@sf.CallGuard.check
def process3(v: TNDArrayIntAny, q: TNDArrayBool) -> TNDArrayFloat64:
    s: TNDArrayFloat64 = np.where(q, 0.5, 0.25)
    return v * s

x = process3(v1, q) # no error, same as mypy
x = process3(v2, q) # no error, same as mypy
v3: TNDArrayFloat64 = np.arange(20, dtype=np.float64) * 0.5
x = process3(v3, q) # error
# static_frame.core.type_clinic.ClinicError:
# In args of (v: ndarray[Any, dtype[signedinteger[Any]]],
# q: ndarray[Any, dtype[bool_]]) -> ndarray[Any, dtype[float64]]
# └── ndarray[Any, dtype[signedinteger[Any]]]
#     └── dtype[signedinteger[Any]]
#         └── Expected signedinteger, provided float64 invalid

StaticFrame 提供了扩展运行时验证的工具,超出了类型检查的范围。使用 typing 模块的 Annotated 类(见 PEP 593),我们可以通过一个或多个 StaticFrame Require 对象来扩展类型规范。例如,要验证一个数组具有 (24,) 的一维形状,我们可以将 TNDArrayIntAny 替换为 Annotated[TNDArrayIntAny, sf.Require.Shape(24)]。要验证浮点数组没有 NaN,我们可以将 TNDArrayFloat64 替换为 Annotated[TNDArrayFloat64, sf.Require.Apply(lambda a: ~a.insna().any())]

在实现一个新函数时,我们可以要求所有输入和输出数组的形状为 (24,)。调用这个函数时,如果使用先前创建的数组,则会引发错误:

from typing import Annotated

@sf.CallGuard.check
def process4(
        v: Annotated[TNDArrayIntAny, sf.Require.Shape(24)],
        q: Annotated[TNDArrayBool, sf.Require.Shape(24)],
        ) -> Annotated[TNDArrayFloat64, sf.Require.Shape(24)]:
    s: TNDArrayFloat64 = np.where(q, 0.5, 0.25)
    return v * s

x = process4(v1, q) # types pass, but Require.Shape fails
# static_frame.core.type_clinic.ClinicError:
# In args of (v: Annotated[ndarray[Any, dtype[int8]], Shape((24,))], q: Annotated[ndarray[Any, dtype[bool_]], Shape((24,))]) -> Annotated[ndarray[Any, dtype[float64]], Shape((24,))]
# └── Annotated[ndarray[Any, dtype[int8]], Shape((24,))]
#     └── Shape((24,))
#         └── Expected shape ((24,)), provided shape (20,)

DataFrame 类型注解

就像字典一样,DataFrame 是一个由许多组件类型组成的复杂数据结构:索引标签、列标签和列值都是不同的类型。

通用地指定 DataFrame 的挑战在于 DataFrame 有可变数量的列,每一列可能有不同的类型。Python 的 TypeVarTuple 可变参数泛型说明符(见 PEP 646),首次在 Python 3.11 中发布,允许定义可变数量的列类型变量。

在 StaticFrame 2.0 中,FrameSeriesIndex 和相关容器变成了通用类型。TypeVarTuple 提供了对可变列类型定义的支持,通过在 typing-extensions 中的实现向下兼容到 Python 3.9。

通用的 Frame 需要两个或更多类型变量:索引类型、列类型,以及一个或多个通过 NumPy 类型指定的列值类型。通用的 Series 需要两个类型变量:索引类型和用于值的 NumPy 类型。Index 本身是通用的,也需要一个作为类型变量的 NumPy 类型。

使用通用规格,浮动类型的 Series,由日期索引,可以注解为 sf.Series[sf.IndexDate, np.float64]。一个以日期作为索引标签,字符串作为列标签,整数和浮动类型作为列值的 Frame 可以注解为 sf.Frame[sf.IndexDate, sf.Index[np.str_], np.int64, np.float64]

给定一个复杂的Frame,推导注解可能会很困难。StaticFrame 提供了 via_type_clinic 接口,在运行时为任何组件提供完整的通用规格:

>>> v4 = sf.Frame.from_fields([range(5), np.arange(3, 8) * 0.5],
columns=('a', 'b'), index=sf.IndexDate.from_date_range('2021-12-30', '2022-01-03'))
>>> v4
<Frame>
<Index>         a       b         <<U1>
<IndexDate>
2021-12-30      0       1.5
2021-12-31      1       2.0
2022-01-01      2       2.5
2022-01-02      3       3.0
2022-01-03      4       3.5
<datetime64[D]> <int64> <float64>

# get a string representation of the annotation
>>> v4.via_type_clinic
Frame[IndexDate, Index[str_], int64, float64]

如同数组一样,将注解存储为类型别名可以实现重用并使函数签名更简洁。以下是一个新函数的定义,带有完全注解的通用 FrameSeries 参数。由于并非所有操作都能静态解析它们的返回类型,因此需要进行 cast

TFrameDateInts = sf.Frame[sf.IndexDate, sf.Index[np.str_], np.int64, np.int64]
TSeriesYMBool = sf.Series[sf.IndexYearMonth, np.bool_]
TSeriesDFloat = sf.Series[sf.IndexDate, np.float64]

def process5(v: TFrameDateInts, q: TSeriesYMBool) -> TSeriesDFloat:
    t = v.index.iter_label().apply(lambda l: q[l.astype('datetime64[M]')]) # type: ignore
    s = np.where(t, 0.5, 0.25)
    return cast(TSeriesDFloat, (v.via_T * s).mean(axis=1))

这些更复杂的注解接口也可以通过 mypy 进行验证。下面,传入了一个没有预期列值类型的 Frame,导致 mypy 报错(如下面的注释所示)。

TFrameDateIntFloat = sf.Frame[sf.IndexDate, sf.Index[np.str_], np.int64, np.float64]
v5: TFrameDateIntFloat = sf.Frame.from_fields([range(5), np.arange(3, 8) * 0.5],
columns=('a', 'b'), index=sf.IndexDate.from_date_range('2021-12-30', '2022-01-03'))

q: TSeriesYMBool = sf.Series([True, False],
index=sf.IndexYearMonth.from_date_range('2021-12', '2022-01'))

x = process5(v5, q)
# tp.py: error: Argument 1 to "process5" has incompatible type
# "Frame[IndexDate, Index[str_], signedinteger[_64Bit], floating[_64Bit]]"; expected
# "Frame[IndexDate, Index[str_], signedinteger[_64Bit], signedinteger[_64Bit]]"  [arg-type]

为了在运行时验证使用相同的类型提示,可以应用 sf.CallGuard.check 装饰器。以下提供了一个包含三列整数的 Frame,而预期的是一个包含两列的 Frame

# a Frame of three columns of integers
TFrameDateIntIntInt = sf.Frame[sf.IndexDate, sf.Index[np.str_], np.int64, np.int64, np.int64]
v6: TFrameDateIntIntInt = sf.Frame.from_fields([range(5), range(3, 8), range(1, 6)],
columns=('a', 'b', 'c'), index=sf.IndexDate.from_date_range('2021-12-30', '2022-01-03'))

x = process5(v6, q)
# static_frame.core.type_clinic.ClinicError:
# In args of (v: Frame[IndexDate, Index[str_], signedinteger[_64Bit], signedinteger[_64Bit]],
# q: Series[IndexYearMonth, bool_]) -> Series[IndexDate, float64]
# └── Frame[IndexDate, Index[str_], signedinteger[_64Bit], signedinteger[_64Bit]]
#     └── Expected Frame has 2 dtype, provided Frame has 3 dtype

为每一列的每个 Frame 注解可能不实际:接口通常需要处理具有可变列数的 FrameTypeVarTuple 通过使用 *tuple[] 表达式(在 Python 3.11 中引入,并通过 Unpack 注解向后移植)来支持这一点。例如,上面的函数可以定义为接受任意数量的整数列,注解为 Frame[IndexDate, Index[np.str_], *tuple[np.int64, ...]],其中 *tuple[np.int64, ...]] 表示零个或多个整数列。

相同的实现可以用更通用的列类型规范进行注解。下面,列值用 np.number[Any] 注解(允许任何类型的数值 NumPy 类型)和 *tuple[] 表达式注解(允许任意数量的列):*tuple[np.number[Any], …]。现在,既不会有 mypy 错误,也不会有 CallGuard 错误。

TFrameDateNums = sf.Frame[sf.IndexDate, sf.Index[np.str_], *tuple[np.number[Any], ...]]

@sf.CallGuard.check
def process6(v: TFrameDateNums, q: TSeriesYMBool) -> TSeriesDFloat:
    t = v.index.iter_label().apply(lambda l: q[l.astype('datetime64[M]')]) # type: ignore
    s = np.where(t, 0.5, 0.25)
    return tp.cast(TSeriesDFloat, (v.via_T * s).mean(axis=1))

x = process6(v5, q) # a Frame with integer, float columns passes
x = process6(v6, q) # a Frame with three integer columns passes

与 NumPy 数组类似,Frame 注解可以将 Require 规范包装在 Annotated 泛型中,从而允许定义额外的运行时验证。

与其他库的类型注解

虽然 StaticFrame 可能是第一个提供完整泛型规范并为静态类型分析和运行时类型验证提供统一解决方案的 DataFrame 库,但其他数组和 DataFrame 库也提供了相关的工具。

PyTorch(2.4.0)中的 Tensor 类和 TensorFlow(2.17.0)中的 Tensor 类都不支持泛型类型或形状规范。虽然这两个库都提供了可以用于进行运行时类型和形状验证的 TensorSpec 对象,但像 mypy 这样的工具不支持静态类型检查。

从 Pandas 2.2.2 开始,Pandas 的 SeriesDataFrame 都不支持泛型类型规范。一些第三方包提供了部分解决方案。例如,pandas-stubs 库为 Pandas API 提供了类型注解,但并未使 SeriesDataFrame 类变成泛型。Pandera 库允许定义 DataFrameSchema 类,可以用于 Pandas DataFrame 的运行时验证。对于与 mypy 进行静态分析,Pandera 提供了替代的 DataFrameSeries 子类,允许使用相同的 DataFrameSchema 类进行泛型规范。该方法无法利用使用泛型 NumPy 类型或解包操作符提供变参泛型表达式的表达能力。

结论

Python 类型注解可以使静态类型分析成为检查代码质量的宝贵工具,能够在代码执行之前发现错误。直到最近,接口可能接受数组或 DataFrame,但无法指定这些容器中包含的类型。现在,NumPy 和 StaticFrame 中已可以完整地指定组件类型,从而允许更强大的类型静态分析。

提供正确的类型注解是一项投资。复用这些注解进行运行时检查提供了两全其美的效果。StaticFrame 的 CallGuard 运行时类型检查器专门用于正确评估完全指定的泛型 NumPy 类型,以及所有泛型 StaticFrame 容器。

提高生存模型的泛化能力

原文:towardsdatascience.com/improving-generalization-in-survival-models-bb7bc045bfc6?source=collection_archive---------8-----------------------#2024-04-05

关于估算和提高员工流失预测准确性的建议

Nicolas LupiTowards Data Science Nicolas Lupi

·发表于 Towards Data Science ·阅读时长 10 分钟·2024 年 4 月 5 日

--

图片由 Israel Andrade 提供,来源于 Unsplash

最近,我在进行生存分析时遇到了一个问题:我拟合的许多模型在理论上表现良好,测试指标很强,但在实际操作中却未能准确预测观察到的真实结果。在本文中,我将讨论如何更好地估算生存模型的表现,并分享一个帮助外推的实用技巧。注意:这里的主要假设是我们在一段时间内对个体进行了多次观察(例如,公司中所有员工的月度观察数据)。

我处理的问题是员工流失的案例。我拥有一家公司若干员工的信息,目标是预测哪些员工在未来最有可能离职。如果你想深入了解员工流失问题,确保查看这篇有用的文章。

传统方法

许多现有的生存分析实现从包含每个个体一条观察数据的数据集开始(如健康研究中的患者、员工流失案例中的员工、客户流失案例中的客户等)。对于这些个体,我们通常有两个关键变量:一个表示感兴趣事件的发生(员工辞职),另一个测量时间(他们在公司工作的时长,直到今天或他们的离职时间)。结合这两个变量,我们接着有一些解释变量,用来预测每个个体的风险。这些特征可以包括员工的职位、年龄或薪酬等。

接下来,大多数现有的实现都会采用一个生存模型(从简单的估计器,如 Kaplan Meier,到更复杂的模型,如集成模型甚至神经网络),将其在训练集上拟合,然后在测试集上评估。通常,这种训练-测试拆分是基于个体观察数据进行的,通常会进行分层拆分。

在我的案例中,我从一个数据集开始,数据集记录了几名员工在公司每月的情况,直到 2023 年 12 月(如果该员工仍在公司),或者直到他们离职的月份——即事件日期:

采用每个员工的最后记录 — 图片来源:作者

为了将我的数据适配到生存分析的案例中,我采集了每个员工的最后一次观察,如上图所示(蓝色点表示在职员工,红色叉表示离职员工)。在这一时刻,对于每个员工,我记录了该事件是否发生(即他们是否仍在职或已经离职),他们当时的在职时长(按月计算),以及他们的所有解释变量。然后,我在这些数据上进行了分层的训练-测试拆分,过程如下:

import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split

# We load our dataset with several observations (record_date) per employee (employee_id)
# The event column indicates if the employee left on that given month (1) or if the employee was still active (0)
df = pd.read_csv(f'{FILE_NAME}.csv')

# Creating a label where positive events have tenure and negative events have negative tenure - required by Random Survival Forest
df_model['label'] = np.where(df_model['event'], df_model['tenure_in_months'], - df_model['tenure_in_months'])

df_train, df_test = train_test_split(df_model, test_size=0.2, stratify=df_model['event'], random_state=42)

拆分完成后,我开始拟合一个模型。在这种情况下,我选择了使用scikit-survival库进行随机生存森林的实验。

from sklearn.preprocessing import OrdinalEncoder
from sksurv.datasets import get_x_y
from sksurv.ensemble import RandomSurvivalForest

cat_features = [] # list of all the categorical features
features = [] # list of all the features (both categorical and numeric)

# Categorical Encoding
encoder = OrdinalEncoder(handle_unknown='use_encoded_value', unknown_value=-1)
encoder.fit(df_train[cat_features])

df_train[cat_features] = encoder.transform(df_train[cat_features])
df_test[cat_features] = encoder.transform(df_test[cat_features])

# X & y
X_train, y_train = get_x_y(df_train, attr_labels=['event','tenure_in_months'], pos_label=1)
X_test, y_test = get_x_y(df_test, attr_labels=['event','tenure_in_months'], pos_label=1)

# Fit the model
estimator = RandomSurvivalForest(random_state=RANDOM_STATE)
estimator.fit(X_train[features], y_train)

# Store predictions
y_pred = estimator.predict(X_test[features])

在使用模型默认设置快速运行后,我对测试集上看到的度量结果感到非常兴奋。首先,我在测试集中获得了一致性指数高于 0.90。一致性指数是衡量模型预测事件顺序的准确度:它反映了是否预测为高风险的员工确实是首先离开公司的员工。指数为 1 表示完美的预测准确性,而 0.5 则表示预测与随机猜测一样糟糕。

我特别感兴趣的是,测试集中离职的员工是否与模型认为风险最高的员工相匹配。在随机生存森林模型的情况下,模型返回了每个观察值的风险分数。我取了测试集中离职员工的比例,并用它来筛选模型认为最有风险的员工。结果非常可靠,风险最高的员工几乎完全与实际离职员工匹配,少数类的 F1 得分超过了 0.90。

from lifelines.utils import concordance_index
from sklearn.metrics import classification_report

# Concordance Index
ci_test = concordance_index(df_test['tenure_in_months'], -y_pred, df_test['event'])
print(f'Concordance index:{ci_test:0.5f}\n')

# Match the most risky employees (according to the model) with the employees who left
q_test = 1 - df_test['event'].mean()

thr = np.quantile(y_pred, q_test)
risky_employees = (y_pred >= thr) * 1

print(classification_report(df_test['event'], risky_employees))

在第一次运行时获得+0.9 的指标应该引起警觉:模型真的能如此自信地预测一个员工是否会留下吗?想象一下:我们提交了预测结果,指出哪些员工最有可能离职。然而,几个月后,人力资源部门联系了我们,表示他们对上个周期离职的员工与我们的预测结果并不完全匹配,至少从我们的测试指标来看,并没有达到预期的离职率。

我们这里有两个主要问题:第一个问题是我们的模型的外推能力没有我们想象的那么好。第二个问题,更糟糕的是,我们无法衡量这种性能缺失。首先,我将展示一种简单的方法,用来估计我们的模型究竟外推得怎么样,然后我将讨论一个可能的原因,为什么它可能没有做到这一点,并且如何缓解这个问题。

估计泛化能力

这里的关键是拥有面板数据,也就是我们在一段时间内记录的多个个体的记录,直到事件发生时或研究结束时(在员工流失的情况下,指的是快照的日期)。我们可以利用这些信息,而不是仅仅丢弃这些数据,只保留每个员工的最后一条记录,从而创建一个更能反映模型未来表现的测试集。这个想法很简单:假设我们有员工直到 2023 年 12 月的每月记录。我们可以往回推,比如 6 个月,假装我们是在六月而不是十二月拍摄了快照。然后,我们将之前离职的员工的最后一条记录(即 2023 年 6 月之前离职的员工)作为正事件,而 2023 年 6 月之后仍然留在公司员工的记录则作为负事件,即使我们已经知道其中一些员工之后离职了。我们假装我们现在还不知道这一点。

我们在 2023 年 6 月拍摄了一张快照,并使用接下来的时间段作为我们的测试集——图像来源:作者

如上图所示,我在六月拍摄了一张快照,所有当时活跃的员工都被视为活跃员工。测试数据集使用了这些六月时活跃的员工,以及他们在那一天的解释变量,并且使用了他们到十二月为止的最新任期:

test_date = '2023-07-01'

# Selecting training data from records before the test date and taking the last observation per employee
df_train = df[df.record_date < test_date].reset_index(drop=True).copy()
df_train = df_train.groupby('employee_id').tail(1).reset_index(drop=True)
df_train['label'] = np.where(df_train['event'], df_train['tenure_in_months'], - df_train['tenure_in_months'])

# Preparing test data with records of active employees at the test date
df_test = df[(df.record_date == test_date) & (df['event']==0)].reset_index(drop=True).copy()
df_test = df_test.groupby('employee_id').tail(1).reset_index(drop=True)
df_test = df_test.drop(columns = ['tenure_in_months','event'])

# Fetching the last tenure and event status for employees in the test dataset
df_last_tenure = df[df.employee_id.isin(df_test.employee_id.unique())].reset_index(drop=True).copy()
df_last_tenure = df_last_tenure.groupby('employee_id').tail(1).reset_index(drop=True)

df_test = df_test.merge(df_last_tenure[['employee_id','tenure_in_months','event']], how='left')
df_test['label'] = np.where(df_test['event'], df_test['tenure_in_months'], - df_test['tenure_in_months'])

我们再次在这组新的训练数据上训练模型,完成后我们为所有在六月份仍然活跃的员工做出预测。然后,我们将这些预测与 2023 年 7 月至 12 月的实际结果进行比较——这就是我们的测试集。如果我们标记为风险最高的员工在学期中离开,而标记为风险最低的员工没有离开,或者在较晚的时间离开,那么我们的模型在推断方面表现良好。通过将分析回溯到过去一段时间,并将最后一段时间用于评估,我们可以更好地理解模型的泛化能力。当然,我们还可以进一步采取一些时间序列交叉验证。例如,我们可以多次迭代这个过程,每次将时间向后移动 6 个月,并评估模型在多个时间段上的准确性。

经过再次训练我们的模型后,我们现在看到性能出现了显著下降。首先,协和指数现在大约为 0.5——相当于一个随机预测器的表现。而且,如果我们根据模型将“n”个最具风险的员工与测试集中离职的“n”个员工进行匹配,我们会看到非常差的分类效果,少数类的 F1 值为 0.15:

所以显然存在一些问题,但至少现在我们能够检测到这一点,而不是被误导。这里的主要结论是,我们的模型在传统的训练/测试集划分下表现良好,但在时间基础的划分下却无法进行良好的推断。这明显表明可能存在某种时间偏差。简而言之,时间依赖性信息正在泄露,导致我们的模型对其过拟合。这在像员工流失问题这样的情况下很常见,当数据集来自某个时间点的快照时。

时间偏差

问题归结为这一点:我们所有的正向观测(离职的员工)都属于过去的日期,而所有的负向观测(当前活跃的员工)都在同一个日期上进行测量——今天。如果有一个特征能够揭示这一点给模型,那么我们将预测的不是风险,而是在预测一个员工是否在 2023 年 12 月或之前被记录下来。这可能是非常微妙的。例如,我们可能使用的一个特征是员工的参与度评分。这个特征可能会表现出某些季节性模式,而在同一时间测量所有活跃员工的参与度评分,肯定会引入一些偏差。比如在 12 月,假期季节时,参与度评分可能会下降。模型会看到所有活跃员工的低评分,因此它可能学会预测每当参与度低时,流失风险也会下降,然而实际上应该是相反的!

到现在为止,一个简单而有效的解决方案应该已经很清楚:我们可以不再取每个活跃员工的最后一次观察记录,而是从他们在公司里的所有历史中随机选择一个月。这样可以大大减少模型过拟合任何我们不希望它拟合的时间模式的可能性。

对于活跃员工,我们选择随机记录,而不是他们的最后一条记录——图片由作者提供

在上面的图片中,我们可以看到现在对于活跃员工,我们涵盖了更广泛的日期范围。我们不再使用 2023 年 6 月的蓝点,而是使用随机的橙点,并记录当时的变量以及他们在公司的任期:

np.random.seed(0)

# Select training data before the test date
df_train = df[df.record_date < test_date].reset_index(drop=True).copy()

# Create an indicator for whether an employee eventually churns within the train set
df_train['indicator'] = df_train.groupby('employee_id').event.transform(max)

# Isolate records of employees who left, and store their last observation
churn = df_train[df_train.indicator==1].reset_index(drop=True).copy()
churn = churn.groupby('employee_id').tail(1).reset_index(drop=True)

# For employees who stayed, randomly pick one observation from their historic records
stay = df_train[df_train.indicator==0].reset_index(drop=True).copy()
stay = stay.groupby('employee_id').apply(lambda x: x.sample(1)).reset_index(drop=True)

# Combine churn and stay samples into the new training dataset
df_train = pd.concat([churn,stay], ignore_index=True).copy()
df_train['label'] = np.where(df_train['event'], df_train['tenure_in_months'], - df_train['tenure_in_months'])
del df_train['indicator']

# Prepare the test dataset similarly, using only the snapshot from the test date
df_test = df[(df.record_date == test_date) & (df.event==0)].reset_index(drop=True).copy()
df_test = df_test.groupby('employee_id').tail(1).reset_index(drop=True)
df_test = df_test.drop(columns = ['tenure_in_months','event'])

# Get the last known tenure and event status for employees in the test set
df_last_tenure = df[df.employee_id.isin(df_test.employee_id.unique())].reset_index(drop=True).copy()
df_last_tenure = df_last_tenure.groupby('employee_id').tail(1).reset_index(drop=True)

df_test = df_test.merge(df_last_tenure[['employee_id','tenure_in_months','event']], how='left')
df_test['label'] = np.where(df_test['event'], df_test['tenure_in_months'], - df_test['tenure_in_months'])

然后我们再次训练模型,并在之前相同的测试集上评估它。我们现在看到的协调指数大约为 0.80。虽然没有之前的+0.90,但它确实比随机机会水平 0.5 有所提升。就我们对员工分类的兴趣而言,尽管仍远未达到之前的+0.9 F1 分数,但与之前的方法相比,我们确实看到了一些提高,尤其是在少数类别上。

结束语

总结一下,以下是我们讨论的主要要点:

  • 重要的是要注意观察记录的日期——如果所有某一事件类别的观察记录共享相同的日期,那么很可能存在某种时间偏差。

  • 如果我们拥有个体的过去观察记录,我们可以通过预留一段时间用于测试,而不是对个体观察记录进行传统的训练-测试拆分,从而更好地评估模型的表现。

  • 如果传统方法与基于时间的测试集拆分之间存在明显的性能下降,这可能是时间偏差的一个迹象。

  • 一种简单的缓解方法(至少部分缓解)是随机选择每个个体的观察记录,而不是取他们的最后一次记录。

希望这次讲解对你有所帮助。如果你在生存分析中遇到类似的问题,我很想知道这种方法是否对你也有效。感谢阅读!

使用模型量化技术提升 CPU 上 LLM 推理速度

原文:towardsdatascience.com/improving-llm-inference-latency-on-cpus-with-model-quantization-28aefb495657?source=collection_archive---------1-----------------------#2024-02-29

图片版权所有 — 由 Nightcafe 创作

探索如何使用量化技术(如 bf16、int8 和 int4 精度)显著提升 CPU 上的推理延迟

Eduardo AlvarezTowards Data Science Eduardo Alvarez

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

--

AI 领域面临的最大挑战之一是需要计算资源来托管大规模的生产级基于 LLM 的应用程序。在大规模应用中,LLM 应用需要冗余、可扩展性和可靠性,这些通常只能在像 CPU 这样的通用计算平台上实现。然而,目前的主流观点认为,CPU 无法在延迟上与高端 GPU 相媲美,无法处理 LLM 推理。

生态系统中一个可以帮助解决 CPU 推理延迟挑战的开源工具是Intel® Extension for PyTorch*(IPEX),它为 Intel 硬件提供了最新的特性优化,能进一步提升性能。IPEX 提供了多种易于实现的优化,充分利用了硬件级指令。本文将深入探讨模型压缩的理论以及 IPEX 提供的现成模型压缩技术。这些压缩技术直接影响基于 Intel 第 4 代和第 5 代 CPU 等通用计算平台的 LLM 推理性能。

应用开发中的推理延迟

仅次于应用安全性和保障性,推理延迟是 AI 应用生产中的最关键参数之一。对于基于 LLM 的应用,延迟或吞吐量通常以 token/秒为单位进行衡量。如下面简化的推理处理顺序所示,token 由语言模型处理,然后被去 token 化为自然语言。

GIF 1. 推理处理顺序 — 图片由作者提供

以这种方式解读推理有时可能会误导我们,因为我们在抽象化的 AI 应用组件分析时,忽略了传统生产软件范式。是的,AI 应用有其独特之处,但归根结底,我们还是在谈论单位时间内的交易。如果我们开始从应用设计的角度将推理视为一种交易,就像任何其他交易一样,那么问题变得不那么复杂。例如,假设我们有一个聊天应用,其要求如下:

  • 每小时平均300 个用户会话

  • 每个用户每次会话平均进行5 次交易(LLM 推理请求)

  • 每次交易生成100 个 token的平均数量

  • 每次会话的10,000ms (10 秒)开销平均用于用户认证、护栏、网络延迟和前/后处理。

  • 用户在与聊天机器人积极互动时,平均需要 30,000ms (30 秒)来响应。

  • 平均总会话时长目标为 3分钟或更短。

如下所示,通过一些简单的估算,我们可以得到 LLM 推理引擎所需的延迟大致计算值。

图 1. 基于不同应用要求计算所需交易和 token 延迟的简单方程式。— 图片由作者提供

在生产环境中达到所需的延迟阈值是一项挑战,特别是当你需要在不增加额外计算基础设施成本的情况下实现这一目标时。本文余下部分将探讨一种通过模型压缩显著提高推理延迟的方法。

模型压缩

模型压缩是一个广泛的术语,因为它涉及多种技术,如模型量化、蒸馏、剪枝等。这些技术的核心目标是减少神经网络的计算复杂度。

GIF 2. 推理处理顺序的说明 — 图片由作者提供

我们今天关注的方法是模型量化,它涉及减少权重的字节精度,有时还包括激活值的精度,从而减少矩阵运算的计算负担和移动较大、高精度值的内存负担。下图展示了将 fp32 权重量化为 int8 的过程。

图 2. 模型量化的可视化表示,从全精度(FP32)到四分之一精度(INT8),理论上将模型复杂度降低 4 倍。 — 图片由作者提供

值得注意的是,从 fp32(全精度)量化到 int8(四分之一精度)所带来的复杂度降低了 4 倍,但并不会导致推理延迟减少 4 倍,因为推理延迟不仅仅与模型本身的特性有关,还涉及更多的因素。

和许多事情一样,并没有一种通用的解决方案,本文将探索我最喜欢的三种使用 IPEX 进行模型量化的技术:

在 bf16 或 fp32 下进行推理

该技术将神经网络中的权重量化为用户定义的精度。这项技术非常适合较小的模型,例如小于 1B 参数的 LLM。

图 3. 简单的 bf16/fp32 示意图,显示 FP32 权重为橙色,半精度量化的 bf16 权重为绿色。 — 图片由作者提供

实现方法相当简单:使用 hugging face transformers,可以将模型加载到内存中,并通过 IPEX 特定的优化函数 ipex.llm.optimize(model, dtype=dtype) 进行优化。通过设置 dtype = torch.bfloat16,,我们可以激活半精度推理功能,这比全精度(fp32)和原始模型提高了推理延迟。

import sys
import os
import torch
import intel_extension_for_pytorch as ipex
from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline

# PART 1: Model and tokenizer loading using transformers
tokenizer = AutoTokenizer.from_pretrained("Intel/neural-chat-7b-v3-3")
model = AutoModelForCausalLM.from_pretrained("Intel/neural-chat-7b-v3-3")

# PART 2: Use IPEX to optimize the model
#dtype = torch.float # use for full precision FP32
dtype = torch.bfloat16 # use for half precision inference
model = ipex.llm.optimize(model, dtype=dtype)

# PART 3: Create a hugging face inference pipeline and generate results
pipe = pipeline("text-generation", model=model, tokenizer=tokenizer)
st = time.time()
results = pipe("A fisherman at sea...",  max_length=250)
end = time.time()
generation_latency = end-st

print('generation latency: ', generation_latency)
print(results[0]['generated_text'])

我们将探索的三种压缩技术中,这种实现方式是最简单的(按代码行数计算),并且相对于未量化的基线,提供了最小的净改进。

SmoothQuant (int8)

该技术解决了量化 LLM 的核心挑战,其中包括处理跨所有层和标记的激活通道中大幅度的离群值,这是传统量化技术难以有效处理的常见问题。这项技术对模型中的权重和激活值进行联合数学变换。该变换战略性地减少了激活值中离群值与非离群值之间的差异,尽管这会以增加权重离群值比例为代价。这个调整使得 Transformer 层“适合量化”,使得 int8 量化得以成功应用,而不会降低模型质量。

图 4. 简单的 SmoothQuant 示意图,权重以圆形表示,激活值以三角形表示。该图展示了两个主要步骤:(1)应用缩放器进行平滑,和(2)量化为 int8 — 图片由作者提供

以下是一个简单的 SmoothQuant 实现 — 省略了创建 DataLoader 的代码,因为这是 PyTorch 的一个常见且文档完善的原则。SmoothQuant 是一种准确度感知的后训练量化方法,这意味着通过提供校准数据集和模型,你将能够提供一个基准并限制语言建模的退化。校准模型生成量化配置,然后将其与 SmoothQuant 映射一起传递给 ipex.llm.optimize()。执行后,应用 SmoothQuant,并可以使用 .generate() 方法测试模型。

import torch
import intel_extension_for_pytorch as ipex
from intel_extension_for_pytorch.quantization import prepare
import transformers

# PART 1: Load model and tokenizer from Hugging Face + Load SmoothQuant config mapping
tokenizer = AutoTokenizer.from_pretrained("Intel/neural-chat-7b-v3-3")
model = AutoModelForCausalLM.from_pretrained("Intel/neural-chat-7b-v3-3")
qconfig = ipex.quantization.get_smooth_quant_qconfig_mapping()

# PART 2: Configure calibration
# prepare your calibration dataset samples
calib_dataset = DataLoader({Your dataloader parameters})
example_inputs = # provide a sample input from your calib_dataset
calibration_model = ipex.llm.optimize(
  model.eval(),
  quantization_config=qconfig,
)
prepared_model = prepare(
  calibration_model.eval(), qconfig, example_inputs=example_inputs
)
with torch.no_grad():
  for calib_samples in enumerate(calib_dataset):
    prepared_model(calib_samples)
prepared_model.save_qconf_summary(qconf_summary=qconfig_summary_file_path)

# PART 3: Model Quantization using SmoothQuant
model = ipex.llm.optimize(
  model.eval(),
  quantization_config=qconfig,
  qconfig_summary_file=qconfig_summary_file_path,
)

# generation inference loop
with torch.inference_mode():
    model.generate({your generate parameters})

SmoothQuant 是一种强大的模型压缩技术,并且显著提高了推理延迟,相较于全精度模型。尽管如此,它仍然需要一些前期工作来准备校准数据集和模型。

仅权重量化(int8 和 int4)

与传统的同时对激活和权重进行 int8 量化相比,仅权重量化(WOQ)在性能和准确性之间提供了更好的平衡。值得注意的是,int4 WOQ 在计算之前需要去量化为 bf16/fp16(图 4),这会引入一定的计算开销。一个基本的 WOQ 技术,张量级的非对称最近舍入(RTN)量化,存在挑战,并且常常导致准确性下降(来源)。然而,文献(Zhewei Yao, 2022)建议,对模型权重进行分组量化有助于保持准确性。由于权重仅在计算时去量化,即便多出了这一步,仍然保持了显著的内存优势。

图 5. 简单的仅权重量化示意图,预量化的权重为橙色,量化后的权重为绿色。请注意,这显示的是初步量化到 int4/int8 和去量化到 fp16/bf16 以进行计算步骤。 — 图片来自作者

以下 WOQ 实现展示了使用此技术从 Hugging Face 量化模型所需的几行代码。与前面的实现一样,我们首先从 Hugging Face 加载模型和标记器。我们可以使用 get_weight_only_quant_qconfig_mapping() 方法来配置 WOQ 配方。然后将该配方与模型一起传递给 ipex.llm.optimize() 函数以进行优化和量化。量化后的模型可以通过 .generate() 方法用于推理。

 # requirements
#intel-extension-for-pytorch==2.2
#transformers==4.35.2
#torch==2.2.0 

import torch
import intel_extension_for_pytorch as ipex
from transformers import AutoTokenizer, AutoModelForCausalLM

# PART 1: Model and tokenizer loading
tokenizer = AutoTokenizer.from_pretrained("Intel/neural-chat-7b-v3-3")
model = AutoModelForCausalLM.from_pretrained("Intel/neural-chat-7b-v3-3")

# PART 2: Preparation of quantization config
qconfig = ipex.quantization.get_weight_only_quant_qconfig_mapping(
  weight_dtype=torch.qint8, # or torch.quint4x2
  lowp_mode=ipex.quantization.WoqLowpMode.NONE, # or FP16, BF16, INT8
)
checkpoint = None # optionally load int4 or int8 checkpoint

# PART 3: Model optimization and quantization
model = ipex.llm.optimize(model, quantization_config=qconfig, low_precision_checkpoint=checkpoint)

inputs = tokenizer("I love learning to code...", return_tensors="pt").input_ids

# PART 4: Inference output generation
with torch.inference_mode():
    tokens = model.generate(
        inputs,
        max_new_tokens=64,
)

print(tokenizer.decode(tokens[0], skip_special_tokens=True))

如你所见,WOQ 提供了一种强大的方法,可以将模型压缩到原始大小的一小部分,同时对语言建模能力的影响有限。

结论与讨论

作为英特尔的工程师,我与英特尔的 IPEX 工程团队密切合作。这使我对其优势和开发路线图有了独特的洞察力,成为了 IPEX 的首选工具。然而,对于那些希望简化开发、无需管理额外依赖的开发者,PyTorch 提供了三种量化方法:急切模式、FX 图模式(正在维护中)和 PyTorch 2 导出量化,提供了强大且不那么专业的替代方案。

无论选择哪种技术,模型压缩技术都会导致一定程度的语言建模性能损失,尽管在许多情况下低于 1%。因此,在追求量化之前,评估应用的容错能力并建立基准是至关重要的,这包括全精度(FP32)和/或半精度(BF16/FP16)的模型性能。

在利用某些上下文学习的应用中,如检索增强生成(RAG),模型压缩可能是一个极好的选择。在这些情况下,关键任务知识在推理时被传输给模型,因此即使在容错性较低的应用中,风险也大大降低。

量化是解决 LLM 推理延迟问题的绝佳方法,无需升级或扩展计算基础设施。无论你的使用案例是什么,都值得探索,而 IPEX 提供了一个不错的起点,只需要几行代码。

有几个激动人心的尝试:

  • 在英特尔开发者云的免费 Jupyter 环境中测试本教程中的示例代码。

  • 在加速器上运行的现有模型以完全精度测试,并在 CPU 上以 int4/int8 进行测试。

  • 探索这三种技术,并确定哪种最适合你的使用案例。确保比较语言建模性能的损失,而不仅仅是延迟。

  • 将你的量化模型上传到 Hugging Face 模型中心!如果你上传了,请告诉我——我很想看看!

感谢阅读!别忘了关注 我的个人资料,获取更多类似的文章

通过复杂推理提高 RAG 回答质量

原文:towardsdatascience.com/improving-rag-answer-quality-through-complex-reasoning-2608ec6c2a65?source=collection_archive---------3-----------------------#2024-07-24

展示使用 DSPy 和 Indexify 构建多跳检索系统的过程

Sachin KhandewalTowards Data Science Sachin Khandewal

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

--

TLDR;

  • 在本文中,我们将探索多跳检索及其如何被利用来构建需要复杂推理的 RAG 系统。

  • 我们将通过在医疗领域构建一个 Q&A 聊天机器人,使用 Indexify、OpenAI 和 DSPy,来展示这一技术。

  • 多跳思维链 RAG 如何高效回答复杂问题。

引言

检索增强生成(RAG)系统已经成为构建 LLM(大语言模型)驱动应用程序的强大方法。RAG 系统首先通过检索模型从外部知识源中检索信息,然后利用这些信息来提示 LLM 生成响应。

然而,基础的 RAG 系统(也称为简单 RAG)在处理需要对多个信息片段进行推理的复杂查询时可能会面临挑战。这时,多跳检索就发挥了作用。

在多跳检索中,系统通过多个步骤或“跳跃”收集信息,以回答复杂问题或收集详细信息。这种技术在高级问答系统中很常见,其中多个来源或文档包含回答问题所需的信息。

我如何使用聚类技术改进分块并构建更好的 RAGs

原文:towardsdatascience.com/improving-rag-chunking-with-clustering-03c1cf41f1cd?source=collection_archive---------1-----------------------#2024-09-04

它既快速又具有成本效益

Thuwarakesh MurallieTowards Data Science Thuwarakesh Murallie

·发布于Towards Data Science ·阅读时长 7 分钟·2024 年 9 月 4 日

--

图片来源:Eaters Collective via Unsplash

当我的句子之间相距甚远时,语义分块崩溃了。

我第一次尝试语义分块时失败了,因为远距离的句子共享了太多的意义。然而,代理性方法在大多数情况下成本高且速度过慢。我想在两者之间找到一个平衡点,我希望它既能保持合理的准确性,又能更便宜。

RAG 应用程序在很大程度上依赖于分块策略。更好的分块能带来更好的响应。有许多方式可以对文本进行分块。最简单且最流行的是递归字符分块,稍微复杂但有帮助的是语义分块。更接近人类思维的方式是代理性分块。

如果你是新手,可以查看我之前关于分块的文章。

## 如何实现接近人类水平的 RAG 分块性能

昂贵但强大的分割技术,用于优化 RAG 检索

towardsdatascience.com

使用重排序器提升 RAG 性能

原文:towardsdatascience.com/improving-rag-performance-using-rerankers-6adda61b966d?source=collection_archive---------9-----------------------#2024-06-25

关于如何使用重排序器来改善您的 RAG 流水线的教程

Het TrivediTowards Data Science Het Trivedi

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

--

作者使用 Stable Diffusion XL 创建

介绍

RAG 是工程师在构建 LLM 应用程序时最早尝试的工具之一。它足够易于理解且简单易用。使用向量搜索的主要动机是收集足够相关的上下文,以便 LLM 的输出质量更高。

尽管向量搜索开箱即用时表现相当不错,但仍然存在许多结果不尽如人意的情况。例如,在向量嵌入中,top k 的结果可能并不包含所有相关信息。为了解决这个问题,可以将 top k 设置为更大的值。然而,这也带来了新的问题。

文档数量超过了 LLM 上下文窗口的大小

尽管 LLM 支持更大的上下文窗口,但仍然有信息量的限制。top k 值越高,将所有信息适配到上下文中的难度就越大。尽管嵌入是按余弦相似度排序的,但这并不能保证最相关的内容排在最前面。这部分是因为向量搜索通常依赖预先计算的嵌入,而这些嵌入可能并不…

改善含有大量零值的对象(或细胞)计数分析

原文:towardsdatascience.com/improving-the-analysis-of-object-or-cells-counts-with-lots-of-zeros-f343d1430637?source=collection_archive---------6-----------------------#2024-04-27

使用 brms 的障碍模型和零膨胀模型方法

Daniel Manrique-CastanoTowards Data Science Daniel Manrique-Castano

·发表于Towards Data Science ·13 分钟阅读·2024 年 4 月 27 日

--

小鼠大脑中,脑缺血后 NeuN 和 GFAP 的染色。Manrique-Castano et al。 (2024) (CC-BY)。

计数是生物医学研究中的一项基本任务,特别是在分析细胞群体时。试想一下,凝视在一个微小的大脑区域内数不清的细胞——有时其数量达到数百甚至数千。然而,在其他区域,这些数量可能会减少到几乎没有。

挑战出现在我们如何分析这些计数值上。对于大数值,假设正态分布的线性模型通常可以作为合理的近似。虽然不是最优的,它们为初步分析提供了一个逻辑框架。然而,当细胞计数较低或主要为零时,情况会发生显著变化。在这种情况下,传统的线性模型(例如我们在 GraphPad 中运行的 t 检验)会失效,失去其有效性和相关性。

作为研究人员,我们必须追求更好的方法,超越 t 检验和方差分析(ANOVA)。本文旨在探索更准确反映我们数据现实的替代统计方法,特别是在处理低计数或零计数时。通过采用更适合的方法,我们可以提高研究结果的精确度,深化对细胞群体的理解。

首先,让我们加载必要的库,并为我们的图表创建一个视觉主题。

library(ggplot2)
library(brms)
library(ggdist)
library(easystats)
library(dplyr)
library(modelr)
library(patchwork)
library(tibble)
library(tidybayes)

logit2prob <- function(logit){
  odds <- exp(logit)
  prob <- odds / (1 + odds)
  return(prob)
}

Plot_theme <- theme_classic() +
  theme(
      plot.title = element_text(size=18, hjust = 0.5, face="bold"),
      plot.subtitle = element_text(size = 10, color = "black"),
      plot.caption = element_text(size = 12, color = "black"),
      axis.line = element_line(colour = "black", size = 1.5, linetype = "solid"),
      axis.ticks.length=unit(7,"pt"),

      axis.title.x = element_text(colour = "black", size = 16),
      axis.text.x = element_text(colour = "black", size = 16, angle = 0, hjust = 0.5),
      axis.ticks.x = element_line(colour = "black", size = 1),

      axis.title.y = element_text(colour = "black", size = 16),
      axis.text.y = element_text(colour = "black", size = 16),
      axis.ticks.y = element_line(colour = "black", size = 1),

      legend.position="right",
      legend.direction="vertical",
      legend.title = element_text(colour="black", face="bold", size=12),
      legend.text = element_text(colour="black", size=10),

      plot.margin = margin(t = 10,  # Top margin
                             r = 2,  # Right margin
                             b = 10,  # Bottom margin
                             l = 10) # Left margin
      ) 

计数零值有什么问题?

在许多研究中,比如图表中显示的(1),我们会遇到低细胞计数的挑战,特别是在其中一个组——这里标为黑色——似乎被零计数所主导。这种情况在生物医学文献中并不罕见,简单的线性模型通常被用来分析这些数据。

图 1:CD3+细胞,Baraibar 等人(2020)(CC-BY)

然而,在低计数或零计数的情况下使用这些模型可能会存在问题。如果没有访问原始数据集——这也是研究人员通常不分享的——就很难全面评估分析的有效性。为了更好地理解这个问题,我们来看一下我自己研究中的一个独立数据集。几年前,我承担了在一个名为室管区(SVZ)的特定脑区内进行缺血事件后 BrdU+细胞计数的任务。这一经历让我倾向于评估更合适的统计方法。

这些是数据:

Svz_data <- read.csv("Data/CellCounts.csv")
Svz_data$Hemisphere <- factor(Svz_data$Hemisphere, levels = c("Contralateral", "Ipsilateral"))
head(Svz_data)

我们可以通过可视化看到,对侧半球有大量的零细胞计数。如果我们使用箱型图,可以获得更清晰的角度:

ggplot(Svz_data, aes(x = Hemisphere, y = Cells)) +
  geom_boxplot() +
  labs(x = "Hemisphere", y = "Number of cells", title = "Cells by hemisphere") +
  Plot_theme +
  theme(legend.position = "top", legend.direction = "horizontal")

图 2:按半球划分的细胞计数

图 2 生动地展示了半球之间在细胞计数上的显著差异。为了研究当我们将典型(而可怕的)线性模型应用于此类数据时会发生什么,我们将通过使用brms进行实践演示。这将帮助我们理解在传统框架下,基于正态分布假设对这些变异进行分析时的影响。

在这个示例中,我将拟合一个线性模型,其中因子变量“hemisphere”是唯一的细胞计数预测变量:

lm_Fit <- brm(Cells ~ Hemisphere, 
           data = Svz_data, 
           # seed for reproducibility purposes
           seed = 8807,
           control = list(adapt_delta = 0.99),
           # this is to save the model in my laptop
           file    = "Models/2024-04-19_CountsZeroInflated/lm_Fit.rds",
           file_refit = "never")

# Add loo for model comparison
lm_Fit <- 
  add_criterion(lm_Fit, c("loo", "waic", "bayes_R2"))

你敢打赌结果会是什么吗?我们来看看:

如果你愿意,你可以使用lm拟合一个频率学派(OLS)模型,肯定会得到相同的结果。在这些结果中,截距代表对侧半球的细胞计数估计,作为我们的参考组。然而,当使用正态分布处理包含大量零计数或接近零值的数据时,会出现显著的不一致性。在这种情况下,模型会“不恰当地”预测我们半球的细胞计数可能是负值,例如-1 到-2 个细胞,并且置信区间范围为-1.5 到 3.7。这种预测是根本性错误的,因为它忽视了细胞作为只能取非负整数值的实体的固有性质。

这个问题源于我们的模型在当前形式下无法理解它所处理的数据的真实特征。相反,它仅仅按照我们的指令——尽管是错误的——将数据拟合到线性模型中。这种常见的疏忽常常导致研究人员通过应用 t 检验和方差分析(ANOVA)进一步加剧问题,从而在一个本质上不合理的模型上叠加额外的分析。作为研究人员,我们必须认识到并利用我们的能力和工具,开发和使用更合适、逻辑上更合理的建模方法,尊重数据的固有特性。

让我们使用伟大的TidyBayes包(2)绘制结果,作者是伟大的Matthew Kay

Svz_data %>%
  data_grid(Hemisphere) %>%
  add_epred_draws(lm_Fit) %>%
  ggplot(aes(x = .epred, y = Hemisphere)) +
  labs(x = "Number of cells") +
  stat_halfeye() +
  geom_vline(xintercept = 0) +
  Plot_theme

图 3:按半球划分的细胞计数的后验分布

如果我们执行pp_check来比较观测值与模型预测,我们也可以看到这种不一致性:

图 4:后验预测检查高斯模型

我们再次遇到细胞计数预测低于零的非理性结果。作为科学家,反思我们的模型是否适合用来解释它们所要解释的数据至关重要。这个思考引导我们走向生成模型,这些模型的建立前提是它们能够合理地生成观察到的数据。显然,目前使用的线性模型未能满足这一标准。它预测了细胞计数不可能出现的负值。让我们尝试找到一个更好的模型。

处理大量零值

零膨胀模型有效地捕捉了以零为主的数据集的细微差别。它通过区分两个不同的过程来运作:1)确定结果是否为零,2)预测非零结果的值。这种双重方法特别适用于提出类似“是否存在细胞,如果有,多少个?”的问题。

对于处理具有大量零的数据集,我们采用如hurdle_poisson()Zero_inflated_poisson等模型,这些模型专为标准计数模型(如泊松模型或负二项模型)无法有效应用的场景设计(3)。宽泛地说,hurdle_poisson()Zero_inflated_poisson之间的一个关键区别在于,后者引入了一个额外的概率成分,专门用于处理零值,从而增强了它们处理零不仅仅是常见而且是显著的情况的能力。我们将在使用brms的建模策略中看到这些特性带来的影响。

拟合 hurdle_poisson 模型

让我们首先在我们的建模方案中使用hurdle_poisson()分布:

Hurdle_Fit1 <- brm(Cells ~ Hemisphere, 
           data = Svz_data, 
           family = hurdle_poisson(),
           # seed for reproducibility purposes
           seed = 8807,
           control = list(adapt_delta = 0.99),
           # this is to save the model in my laptop
           file    = "Models/2024-04-19_CountsZeroInflated/Hurdle_Fit1.rds",
           file_refit = "never")

# Add loo for model comparison
Hurdle_Fit1 <- 
  add_criterion(Hurdle_Fit1, c("loo", "waic", "bayes_R2"))

让我们使用标准总结函数来看结果。

summary(Hurdle_Fit1)

鉴于这种家族分布,估计值以对数尺度显示(mu = log)。从实际角度看,这意味着对侧脑室下区(SVZ)的细胞数量可以表示为 exp(1.11) = 3.03。同样,同侧半球的细胞数量预计为 exp(1.07) = 2.91 倍。这些结果与我们的预期非常一致,并为两侧半球之间的细胞分布提供了连贯的解释。

此外,“家庭特定参数”中的hu参数揭示了观察到零细胞计数的可能性。它表明零发生的概率为 38%。这个概率突出了零膨胀模型方法的必要性,并证明了在我们的分析中使用这一方法的合理性。

为了更好地可视化这些发现的意义,我们可以利用conditional_effects函数。brms包中的这个工具允许我们绘制不同预测变量对响应变量的估计效应,提供清晰的图形表示,展示预测变量如何影响预期的细胞计数。

Hurdle_CE <- 
  conditional_effects(Hurdle_Fit1)

Hurdle_CE <- plot(Hurdle_CE, 
       plot = FALSE)[[1]]

Hurdle_Com <- Hurdle_CE + 
  Plot_theme +
  theme(legend.position = "bottom", legend.direction = "horizontal")

Hurdle_CE_hu <- 
  conditional_effects(Hurdle_Fit1, dpar = "hu")

Hurdle_CE_hu <- plot(Hurdle_CE_hu, 
       plot = FALSE)[[1]]

Hurdle_hu <- Hurdle_CE_hu + 
  Plot_theme +
  theme(legend.position = "bottom", legend.direction = "horizontal")

Hurdle_Com | Hurdle_hu

图 5:障碍拟合的条件效应

这些图表比我们的第一个模型更合乎逻辑。左侧的图展示了模型的两部分(“mu”和“hu”)。此外,如果该模型合适,我们应该能看到在使用pp_check时,预测结果更加一致:

pp_check(Hurdle_Fit1, ndraws = 100) +
  labs(title = "Hurdle regression") +
  theme_classic()

图 6:后验预测检查障碍模型

正如预期的那样,我们的模型预测在 0 处有一个下边界。

建模数据的离散性

从图 5 右侧图表中观察数据,揭示了我们的实证发现与我们对该主题的理论理解之间的差异。根据已有的知识,我们期望在同侧大脑半球的脑室下区(SVZ)中,非零细胞计数的概率更高,特别是在受伤后。这是因为同侧 SVZ 通常会成为细胞活动的中心,受伤后会有显著的细胞增殖。我们的数据表明该区域普遍存在非零计数,支持这一生物学预期。

然而,当前模型的预测并未完全与这些见解相符。这种偏差凸显了在统计建模中融入科学理解的重要性。仅仅依赖标准测试而不进行情境适配,可能会导致误导性的结论。

为了解决这个问题,我们可以通过专门调整hu参数来优化模型,hu代表零发生的概率。这样可以更准确地反映同侧大脑半球 SVZ(脑室下区)的预期生物学活动。然后我们建立了第二个障碍模型:

Hurdle_Mdl2 <- bf(Cells ~ Hemisphere, 
                   hu ~ Hemisphere)

Hurdle_Fit2 <- brm(
           formula = Hurdle_Mdl2,
           data = Svz_data, 
           family = hurdle_poisson(),
           # seed for reproducibility purposes
           seed = 8807,
           control = list(adapt_delta = 0.99),
           # this is to save the model in my laptop
           file    = "Models/2024-04-19_CountsZeroInflated/Hurdle_Fit2.rds",
           file_refit = "never")

# Add loo for model comparison
Hurdle_Fit2 <- 
  add_criterion(Hurdle_Fit2, c("loo", "waic", "bayes_R2"))

首先让我们看看结果图是否符合我们的假设:

Hurdle_CE <- 
  conditional_effects(Hurdle_Fit2)

Hurdle_CE <- plot(Hurdle_CE, 
       plot = FALSE)[[1]]

Hurdle_Com <- Hurdle_CE + 
  Plot_theme +
  theme(legend.position = "bottom", legend.direction = "horizontal")

Hurdle_CE_hu <- 
  conditional_effects(Hurdle_Fit2, dpar = "hu")

Hurdle_CE_hu <- plot(Hurdle_CE_hu, 
       plot = FALSE)[[1]]

Hurdle_hu <- Hurdle_CE_hu + 
  Plot_theme +
  theme(legend.position = "bottom", legend.direction = "horizontal")

Hurdle_Com | Hurdle_hu

图 7:障碍拟合的条件效应 2

这种修订后的建模方法似乎是一个实质性的改进。通过专门考虑对侧半球零计数的更高概率(约 75%),该模型现在与观测数据和我们的科学知识更为一致。这一调整不仅反映了该区域预期的较低细胞活动,还提高了我们估计的精确度。通过这些变化,模型现在能更细致地解释受伤后细胞动态。让我们查看总结以及hu参数的变换(不要看其他的),以便通过我们在开始时创建的logit2prob 函数将其可视化为概率尺度。

logit2prob(fixef(Hurdle_Fit2))

尽管细胞数量的估计值相似,hu参数(在 logit 尺度上)告诉我们,在对侧半球观察零计数的概率是:

相反:

显示受损(同侧)半球观察到零细胞计数的概率大幅下降至约 0.23%。这是我们估计的一个显著变化。

现在,让我们探讨一下zero_inflated_poisson()分布家族是否改变了这些见解。

拟合零膨胀泊松模型

由于我们使用hu参数建模同侧和对侧半球之间细胞计数的广泛变化,我们也将在zero_inflated_poisson()模型中拟合这两个部分。这里,模型的计数部分使用“log”链接,而零膨胀部分则使用“logit”链接。这些是与分布家族相关的链接函数,我们将在此不讨论。

Inflated_mdl1 <- bf(Cells ~ Hemisphere,
                    zi ~ Hemisphere)

Inflated_Fit1 <- brm(
           formula = Inflated_mdl1, 
           data = Svz_data, 
           family = zero_inflated_poisson(),
           # seed for reproducibility purposes
           seed = 8807,
           control = list(adapt_delta = 0.99),
           # this is to save the model in my laptop
           file    = "Models/2024-04-19_CountsZeroInflated/Inflated_Fit.rds",
           file_refit = "never")

# Add loo for model comparison
Inflated_Fit1 <- 
  add_criterion(Inflated_Fit1, c("loo", "waic", "bayes_R2"))

在查看结果之前,让我们做一些基本的诊断,以比较观测值和模型预测。

set.seed(8807)

pp_check(Inflated_Fit1, ndraws = 100) +
  labs(title = "Zero-inflated regression") +
  theme_classic()

图 8:零膨胀回归的模型诊断

从图 8 中,我们可以看到,预测结果与观测数据的偏差与障碍模型类似。因此,到目前为止,我们没有重大的变化。

让我们查看数值结果:

logit2prob(fixef(Inflated_Fit1))

在这里,我们确实看到估计值有微小变化。细胞数量的估计相似,可信区间有小幅变化。否则,零数量的参数似乎经历了更大的变化。让我们看看这是否会影响我们的结论:

表示在参考半球观察到零计数的概率大约为 77%。现在,对于受损半球,我们有:

再次,这表明在受伤(同侧)半球观察到零细胞计数的情况大幅减少。根据科学知识来评估结果,我认为这两个模型提供了类似合理的预测。我们的zero_inflated_poisson模型的图形结果如下:

Inflated_CE <- 
  conditional_effects(Inflated_Fit1)

Inflated_CE <- plot(Inflated_CE, 
       plot = FALSE)[[1]]

Inflated_Com <- Inflated_CE + 
  Plot_theme +
  theme(legend.position = "bottom", legend.direction = "horizontal")

Inflated_CE_zi <- 
  conditional_effects(Inflated_Fit1, dpar = "zi")

Inflated_CE_zi <- plot(Inflated_CE_zi, 
       plot = FALSE)[[1]]

Inflated_zi <- Inflated_CE_zi + 
  Plot_theme +
  theme(legend.position = "bottom", legend.direction = "horizontal")

Inflated_Com | Inflated_zi

图 9:膨胀拟合的条件效应

结果似乎与 hurdle 模型相似。然而,我们可以观察到,对侧半球为零的概率估计在zero_inflated_poisson模型中更为严格。原因正如我一开始所解释的,零膨胀模型比hurdle_poisson分布定位了更多的零的概率。让我们比较一下这些模型,结束这篇文章。

模型比较

我们使用loo包执行留一交叉验证(45)。WAIC(6)是你可以在这篇文章中探索的另一种方法。

loo(Hurdle_Fit2, Inflated_Fit1)

即使我们的模型预测相似,"pareto-k-diagnostic"也表明hurdle_poisson模型有一个非常差的值,而这个值在zero_inflated_poisson()模型中转变为。我判断这个“差和非常差”值可能是我们在图 2 中看到的对侧计数的极端观测值所导致的黑点。我们可以拟合另一个不包括该值的模型并重新评估结果。然而,这个程序的目标只是评估这个极端观测值的影响,并告知观众估计结果中可能存在的偏差(应该完全报告)。这是对数据进行透彻且透明解读的方式。我还写了一篇关于极端值的文章,可能对你有兴趣。

我得出结论,这篇文章中执行的建模方法比基于高斯分布的简单线性模型更合适。科学家有责任使用最合适的统计建模工具,这是道德和专业的责任。幸运的是,brms是存在的!

我非常感谢你的评论或反馈,告诉我这段旅程是否对你有帮助。如果你想获得更多关于数据科学及其他主题的优质内容,你可以考虑成为medium 会员

将来,你可以在我的GitHub 网站找到这篇文章的更新版本。

  • 除非另有说明,否则所有图像均是使用所展示的 R 代码生成的。

参考文献

1.I. Baraibar, M. Roman, M. Rodríguez-Remírez, I. López, A. Vilalta, E. Guruceaga, M. Ecay, M. Collantes, T. Lozano, D. Alignani, A. Puyalto, A. Oliver, S. Ortiz-Espinosa, H. Moreno, M. Torregrosa, C. Rolfo, C. Caglevic, D. García-Ros, M. Villalba-Esparza, C. De Andrea, S. Vicent, R. Pío, J. J. Lasarte, A. Calvo, D. Ajona, I. Gil-Bazo, Id1 与 PD-1 联合阻断通过刺激 PD-L1 表达和肿瘤浸润的 CD8+ T 细胞抑制 KRAS 突变型肺癌的肿瘤生长和存活癌症12,3169(2020)。

2. M. Kay, tidybayes: 适用于贝叶斯模型的整洁数据和几何图形(2023;mjskay.github.io/tidybayes/)。

3. C. X. Feng, 零膨胀计数数据建模的零膨胀模型与障碍模型比较。统计分布与应用杂志8(2021),doi:10.1186/s40488–021–00121–4

4. A. Vehtari, J. Gabry, M. Magnusson, Y. Yao, P.-C. Bürkner, T. Paananen, A. Gelman, Loo:高效的留一法交叉验证和 WAIC 用于贝叶斯模型(2022)(可在mc-stan.org/loo/获取)。

5. A. Vehtari, A. Gelman, J. Gabry, 使用留一法交叉验证和 WAIC 进行贝叶斯模型的实用评估统计与计算27,1413–1432(2016)。

6. A. Gelman, J. Hwang, A. Vehtari, 理解贝叶斯模型的预测信息准则统计与计算24,997–1016(2013)。

《为数据科学中的大语言模型辩护:ChatGPT 能为你的数据科学事业做什么,不能做什么》

原文:towardsdatascience.com/in-defense-of-llms-in-data-science-what-chatgpt-can-and-cant-do-for-your-data-science-career-c166047b8fbb?source=collection_archive---------5-----------------------#2024-04-11

观点

ChatGPT 可以将你的数据科学技能提升到一个新高度——前提是你知道如何使用它。

Murtaza AliTowards Data Science Murtaza Ali

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

--

一张数据科学家使用 ChatGPT 的图片,由 ChatGPT 生成。

当 ChatGPT 在 2022 年 11 月首次发布时,大语言模型(Large Language Model)热潮空前巨大。它就像从托尼·斯塔克的实验室中走出来的,我们终于拥有了一种像人类一样进行交流的人工智能。即使对于技术人员来说,它的能力最初也令人震惊,甚至有些可怕。虽然大语言模型(LLM)在那时已经出现一段时间,但 GPT-3 将这一切推向了新的高度。

但随后,问题开始显现出来。机器学习研究人员表示,ChatGPT 会“幻觉”——它经常编造事实,并引用“并不存在”的“来源”。伦理学家警告称,ChatGPT 对学术诚信构成灾难——学生比以往更容易作弊。而且,最重要的是,AI 伦理学研究人员警告称,ChatGPT 并不符合伦理——它的大量训练数据充满了偏见,这在它的回应中有所反映。

这引发了一个两难问题。ChatGPT 确实强大——它肯定可以做很多事情。但与此同时,它远非完美。那么我们应该使用它吗?如果使用,又该如何使用?

深入了解支持向量机(SVM)在线性和非线性分类与回归中的应用

原文:towardsdatascience.com/in-depth-support-vector-machines-svms-for-linear-non-linear-classification-regression-2f743962bfee?source=collection_archive---------6-----------------------#2024-05-19

深入理解 SVM 在背后是如何工作的

Rukshan PramodithaTowards Data Science Rukshan Pramoditha

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

--

照片来自vackground.comUnsplash

支持向量机是一种多用途的机器学习算法,主要用于线性和非线性分类,也可以用于线性和非线性回归。

它属于监督学习类别,需要特征矩阵和标签向量来训练模型。

支持向量机的主要目标是找到一个超平面,将数据点以最佳方式分隔成两个或多个组(类别)。

不同维度中超平面的定义

在机器学习中,超平面是一个线性决策边界,其维度比数据的维度少 1。如果数据被绘制在一个 N 维空间中,超平面具有 N-1 维。

示例:

  • 在一维(1D)空间(直线)中,超平面是一个点(0D)。

  • 在二维(2D)空间中,超平面是一个直线(1D)。

将 LLM 聊天机器人集成到您的网页应用程序中,使用 OpenAI、Python 和 Shiny

原文:towardsdatascience.com/incorporate-an-llm-chatbot-into-your-web-application-with-openai-python-and-shiny-fe78673a5d26?source=collection_archive---------13-----------------------#2024-06-18

将 AI 聊天机器人一步步集成到 Shiny for Python 应用程序中:从 API 设置到用户互动

Deepsha MenghaniTowards Data Science Deepsha Menghani

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

--

BoliviaInteligente提供,照片来源于Unsplash

几天后,我将在即将召开的卡斯卡迪亚数据科学会议上发表关于生成性人工智能的主题演讲。为了这次演讲,我想为会议定制一些内容,因此我创建了一个能够回答关于会议议程问题的聊天机器人。为了展示这个功能,我通过 Shiny for Python 网络应用程序提供了这个聊天机器人。Shiny是一个框架,可以用来创建可以在后台运行代码的互动式网页应用程序。

除了原型开发,Shiny 中提供聊天机器人的一个重要应用场景是回答关于仪表板中各个领域的文档问题。例如,如果仪表板用户想了解图表中客户流失指标是如何创建的,Shiny 应用程序中的聊天机器人可以让用户用自然语言提问,并直接获得答案,而无需翻阅大量文档。

在本文中,我将介绍如何使用 OpenAI 在 Shiny for Python 网络应用程序中集成和服务聊天机器人。让我们开始吧!

轻松提高你回归模型的信任度

原文:towardsdatascience.com/increase-trust-in-your-regression-model-the-easy-way-3349ee5f194c?source=collection_archive---------4-----------------------#2024-11-13

如何使用符合化分位回归

Jonte DanckerTowards Data Science Jonte Dancker

·发表于Towards Data Science ·阅读时间 5 分钟·2024 年 11 月 13 日

--

图片由作者提供

我们必须了解我们的模型在其预测中的确定性,才能做出充分知情的决策。因此,仅返回一个点预测是不够的。它无法告诉我们是否可以信任我们的模型。如果你想知道原因,请查看我下面的文章。

## 不确定性量化及其重要性

如何通过三行代码提升你的机器学习模型

towardsdatascience.com

在文章中,我以分类问题为例。然而,许多实际问题是回归问题。例如,我们想知道模型在预测明天的温度时的确定性如何。

由于温度是一个连续变量,我们希望知道真实温度将位于哪个区间。

区间越宽,模型的不确定性越大。因此,在做决策时,我们应该减少对其的信任。

但我们如何获得这样的预测区间呢?

有两种方法可以考虑。要么使用一组预测区间的模型,要么将点预测转换为……

提高 Transformer 模型效率:通过优化注意力层

原文:towardsdatascience.com/increasing-transformer-model-efficiency-through-attention-layer-optimization-fefa6f87b1d6?source=collection_archive---------2-----------------------#2024-11-18

如何通过“更好”地关注来推动机器学习成本节省

Chaim RandTowards Data Science Chaim Rand

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

--

图片由Andrew Seaman拍摄,来源于Unsplash

在 2017 年具有里程碑意义的论文“Attention Is All You Need”(Vaswani 等人,2017)中首次提出,Transformer 架构被广泛认为是过去十年最具影响力的科学突破之一。Transformer 的核心是注意力机制,这是一种新颖的方法,使得人工智能模型能够通过根据手头任务集中关注输入序列的不同部分,来理解复杂的结构。最初在自然语言处理领域展示成功后,Transformer 架构的成功迅速扩展到许多其他领域,包括语音识别、场景理解、强化学习、蛋白质结构预测等。然而,注意力层是高度资源密集型的,随着这些层成为越来越大模型的标准,其训练和部署的成本也急剧上升。这催生了减少这一核心层计算成本的迫切需求,以提高基于 Transformer 的人工智能模型的效率和可扩展性。

在本文中,我们将探索几个用于优化PyTorch中注意力的工具。我们将重点介绍那些保持注意力层准确性的方法。包括PyTorch SDPAFlashAttentionTransformerEngine注意力、FlexAttentionxFormer注意力。其他通过近似注意力计算来减少计算成本的方法(例如,DeepSpeed 的稀疏注意力LongformerLinformer等)将不予考虑。此外,我们也不会讨论那些对注意力性能有益,但与注意力计算本身无关的通用优化技术(例如,FP8 训练、模型分片和更多)。

值得注意的是,注意力优化是一个活跃的研究领域,新方法几乎定期发布。我们的目标是提高你对一些现有解决方案的认知,并为进一步探索和实验提供基础。我们下面分享的代码仅用于演示目的——我们并不对其准确性、最优性或健壮性做出任何声明。请不要将我们提到的任何平台、库或优化技术视为对其使用的认可。最适合你的选项将很大程度上依赖于你自己用例的具体情况。

非常感谢伊兹哈克·莱维对本文的贡献。

玩具模型

为了便于讨论,我们使用流行的timm Python 包(版本 0.9.7)构建了一个基于视觉 Transformer(ViT)的分类模型。我们将使用这个模型来说明各种注意力内核对性能的影响。

我们首先定义了一个简化的 Transformer 块,通过将注意力函数传递给其构造函数来进行编程。由于注意力实现假设特定的输入张量格式,我们还提供了一个选项来控制格式,确保与我们选择的注意力内核兼容。

# general imports
import os, time, functools

# torch imports
import torch
from torch.utils.data import Dataset, DataLoader
import torch.nn as nn

# timm imports
from timm.models.vision_transformer import VisionTransformer
from timm.layers import Mlp

IMG_SIZE = 224
BATCH_SIZE = 128

# Define ViT settings
NUM_HEADS = 16
HEAD_DIM = 64
DEPTH = 24
PATCH_SIZE = 16
SEQ_LEN = (IMG_SIZE // PATCH_SIZE)**2 # 196

class MyAttentionBlock(nn.Module):
    def __init__(
            self,
            attn_fn,
            format = None,
            dim: int = 768,
            num_heads: int = 12,
            **kwargs
    ) -> None:
        super().__init__()
        self.attn_fn = attn_fn
        self.num_heads = num_heads
        self.head_dim = dim // num_heads
        self.norm1 = nn.LayerNorm(dim)
        self.norm2 = nn.LayerNorm(dim)
        self.qkv = nn.Linear(dim, dim * 3, bias=False)
        self.proj = nn.Linear(dim, dim)
        self.mlp = Mlp(
            in_features=dim,
            hidden_features=dim * 4,
        )
        permute = (2, 0, 3, 1, 4)
        self.permute_attn = functools.partial(torch.transpose,dim0=1,dim1=2)

        if format == 'bshd':
            permute = (2, 0, 1, 3, 4)
            self.permute_attn = nn.Identity()
        self.permute_qkv = functools.partial(torch.permute,dims=permute)

    def forward(self, x_in: torch.Tensor) -> torch.Tensor:
        x = self.norm1(x_in)
        B, N, C = x.shape
        qkv = self.qkv(x).reshape(B, N, 3, self.num_heads, self.head_dim)
        # permute tensor based on the specified format
        qkv = self.permute_qkv(qkv)
        q, k, v = qkv.unbind(0)
        # use the attention function specified by the user
        x = self.attn_fn(q, k, v)
        # permute output according to the specified format
        x = self.permute_attn(x).reshape(B, N, C)
        x = self.proj(x)
        x = x + x_in
        x = x + self.mlp(self.norm2(x))
        return x

我们定义了一个随机生成的数据集,我们将在训练过程中将其输入到我们的模型中。

# Use random data
class FakeDataset(Dataset):
    def __len__(self):
        return 1000000

    def __getitem__(self, index):
        rand_image = torch.randn([3, IMG_SIZE, IMG_SIZE],
                                 dtype=torch.float32)
        label = torch.tensor(data=index % 1000, dtype=torch.int64)
        return rand_image, label 

接下来,我们定义我们的 ViT 训练函数。尽管我们的示例主要演示了训练工作负载,但必须强调的是,在模型推理过程中优化注意力层同样重要,甚至更为重要。

我们定义的训练函数接受定制的 Transformer 块和一个控制是否使用torch.compile的标志。

def train_fn(block_fn, compile):
    torch.random.manual_seed(0)
    device = torch.device("cuda:0")
    torch.set_float32_matmul_precision("high")

    # Create dataset and dataloader
    train_set = FakeDataset()
    train_loader = DataLoader(
        train_set, batch_size=BATCH_SIZE,
        num_workers=12, pin_memory=True, drop_last=True)

    model = VisionTransformer(
       img_size=IMG_SIZE,
       patch_size=PATCH_SIZE,
       embed_dim=NUM_HEADS*HEAD_DIM,
       depth=DEPTH,
       num_heads=NUM_HEADS,
       class_token=False,
       global_pool="avg",
       block_fn=block_fn
    ).to(device)

    if compile:
        model = torch.compile(model)

    # Define loss and optimizer
    criterion = torch.nn.CrossEntropyLoss()
    optimizer = torch.optim.SGD(model.parameters())

    model.train()

    t0 = time.perf_counter()
    summ = 0
    count = 0
    for step, data in enumerate(train_loader):
        # Copy data to GPU
        inputs = data[0].to(device=device, non_blocking=True)
        label = data[1].to(device=device, non_blocking=True)
        with torch.amp.autocast('cuda', enabled=True, dtype=torch.bfloat16):
            outputs = model(inputs)
            loss = criterion(outputs, label)
        optimizer.zero_grad(set_to_none=True)
        loss.backward()
        optimizer.step()

        # Capture step time
        batch_time = time.perf_counter() - t0
        if step > 20:  # Skip first steps
            summ += batch_time
            count += 1
        t0 = time.perf_counter()
        if step > 100:
            break
    print(f'average step time: {summ / count}')

# define compiled and uncompiled variants of our train function
train = functools.partial(train_fn, compile=False)
train_compile = functools.partial(train_fn, compile=True)

在下面的代码块中,我们定义了一个 PyTorch 原生的注意力函数,并用它来训练我们的 ViT 模型:

def attn_fn(q, k, v):
    scale = HEAD_DIM ** -0.5
    q = q * scale
    attn = q @ k.transpose(-2, -1)
    attn = attn.softmax(dim=-1)
    x = attn @ v
    return x

block_fn = functools.partial(MyAttentionBlock, attn_fn=attn_fn)

print('Default Attention')
train(block_fn)
print('Compiled Default Attention')
train_compile(block_fn)

我们在一台配备NVIDIA H100的机器上运行此测试,使用CUDA 12.4PyTorch 2.5.1。未经编译的变体平均步骤时间为 370 毫秒(ms),而编译后的变体提高到 242 毫秒。我们将使用这些结果作为基准,在考虑其他执行注意力计算的解决方案时进行对比。

PyTorch SDPA

提升 PyTorch 中注意力层性能的最简单方法之一是使用scaled_dot_product_attention(SDPA)函数。目前在 beta 阶段,PyTorch SDPA 整合了多个内核级优化,并根据输入的特性动态选择最有效的优化方法。支持的后端(截至目前)包括:FlashAttention-2Memory-Efficient Attention,一个基于 C++的数学注意力,和CuDNN。这些后端将高级操作融合在一起,同时利用 GPU 级优化来提高计算效率和内存利用率。

SDPA 正在不断发展,新的和改进的后端实现定期推出。保持跟进最新的 PyTorch 发布版本是利用最新性能改进的关键。例如,PyTorch 2.5引入了更新的CuDNN 后端,其中包含一个专门为NVIDIA Hopper 架构 GPU 训练量身定制的SDPA 原语

在下面的代码块中,我们遍历支持的后端列表,并评估每个后端的训练运行时性能。我们使用一个辅助函数,set_sdpa_backend,来编程 SDPA 后端:

from torch.nn.functional import scaled_dot_product_attention as sdpa

def set_sdpa_backend(backend):
    torch.backends.cuda.enable_flash_sdp(False)
    torch.backends.cuda.enable_mem_efficient_sdp(False)
    torch.backends.cuda.enable_math_sdp(False)
    torch.backends.cuda.enable_cudnn_sdp(False)

    if backend in ['flash_sdp','all']:
        torch.backends.cuda.enable_flash_sdp(True)
    if backend in ['mem_efficient_sdp','all']:
        torch.backends.cuda.enable_mem_efficient_sdp(True)
    if backend in ['math_sdp','all']:
        torch.backends.cuda.enable_math_sdp(True)
    if backend in ['cudnn_sdp','all']:
        torch.backends.cuda.enable_cudnn_sdp(True)

for backend in ['flash_sdp', 'mem_efficient_sdp',
                'math_sdp', 'cudnn_sdp']:
    set_sdpa_backend(backend)
    block_fn = functools.partial(MyAttentionBlock,
                                 attn_fn=sdpa)

    print(f'PyTorch SDPA - {backend}')
    train(block_fn)
    print(f'Compiled PyTorch SDPA - {backend}')
    train_compile(block_fn)

我们在下面的表格中总结了我们的阶段性结果

各种注意力函数的步骤时间(越低越好)— 按作者分类

尽管在 Eager 模式下选择 SDPA 后端会对性能产生显著影响,但通过 模型编译 进行的优化似乎掩盖了不同注意力内核之间的差异。再次提醒,我们不应仅从这些结果得出结论,因为不同注意力函数的性能影响可能会根据特定的模型和使用场景而有所不同。

第三方注意力内核

虽然 PyTorch SDPA 是一个很好的起点,但使用第三方注意力内核可以进一步加速你的 ML 工作负载。这些替代方案通常提供更多的灵活性,提供更广泛的注意力配置选项。某些方案还可能包括针对特定硬件加速器或新一代 GPU 架构的优化。

在本节中,我们将探讨一些可用的第三方注意力核,并评估它们对运行时性能的潜在影响。

FlashAttention-3

虽然 Pytorch SDPA 支持 FlashAttention 后端,但更先进的 FlashAttention 实现可以在 flash-attn 库中找到。在这里,我们将探讨 FlashAttention-3 的测试版发布,它的速度比 FlashAttention-2 快 2 倍。鉴于其开发处于早期阶段,FlashAttention-3 只能直接从 GitHub 仓库 安装,并且其使用仅限于特定的头维度。此外,它尚不支持模型编译。在下面的代码块中,我们将配置我们的 Transformer 块以使用 flash-attn-3,并将注意力输入格式设置为 "bshd"(batch,sequence,head,depth),以符合该库的要求。

# flash attention 3
from flash_attn_interface import flash_attn_func as fa3
attn_fn = lambda q,k,v: fa3(q,k,v)[0]
block_fn = functools.partial(MyAttentionBlock,
                             attn_fn=attn_fn,
                             format='bshd')

print(f'Flash Attention 3')
train(block_fn)

结果的步骤时间为 240 毫秒,比 SDPA flash-attn 快 5%。

Transformer Engine

Transformer Engine(TE)是一个专门设计的库,用于加速 NVIDIA GPU 上的 Transformer 模型。TE 定期更新,采用优化技术,充分利用最新 NVIDIA 硬件和软件的能力,使用户能够在这些功能被集成到通用框架如 PyTorch 之前,便可以访问到专门的内核。

在下面的代码块中,我们使用了来自 TE 版本 1.11.0DotProductAttention。与 PyTorch SDPA 类似,TE 支持多种后端,这些后端通过环境变量控制。在此我们演示了 NVTE_FUSED_ATTN 后端的使用。

def set_te_backend(backend):
    # must be applied before first use of
    # transformer_engine.pytorch.attention
    os.environ["NVTE_FLASH_ATTN"] = '0'
    os.environ["NVTE_FUSED_ATTN"] = '0'
    os.environ["NVTE_UNFUSED_ATTN"] = '0'
    if backend == 'flash':
        os.environ["NVTE_FLASH_ATTN"] = '1'
    if backend == 'fused':
        os.environ["NVTE_FUSED_ATTN"] = '1'
    if backend == 'unfused':
        os.environ["NVTE_UNFUSED_ATTN"] = '1'

from transformer_engine.pytorch.attention import DotProductAttention
set_te_backend('fused')
attn_fn = DotProductAttention(NUM_HEADS, HEAD_DIM, NUM_HEADS,
                              qkv_format='bshd',
                              # disable masking (default is causal mask)
                              attn_mask_type='no_mask')

block_fn = functools.partial(MyAttentionBlock,
                             attn_fn=attn_fn,
                             format='bshd')

print(f'Transformer Engine Attention')
train(block_fn)
print(f'Compiled Transformer Engine Attention')
train_compile(block_fn)

TE 注意力在 Eager 和编译模型变体中的平均步骤时间分别为 243 毫秒和 204 毫秒。

XFormer 注意力

PyTorch SDPA 的内存高效后端基础是由 xFormers 库提供的注意力内核。我们可以再次访问源代码,以便受益于最新的内核优化和完整的 API 功能。在以下代码块中,我们使用了来自 xFormers 版本 0.0.28memory_efficient_attention 操作符。

# xformer memory efficient attention
from xformers.ops import memory_efficient_attention as mea
block_fn = functools.partial(MyAttentionBlock,
                             attn_fn=mea,
                             format='bshd')

print(f'xFormer Attention ')
train(block_fn)
print(f'Compiled xFormer Attention ')
train_compile(block_fn)

这种急切模型变体的平均步长时间为 246 毫秒,比 SDPA 内存高效内核快了 10.5%。编译版的步长时间为 203 毫秒。

结果

以下表格总结了我们的实验:

各种注意力函数的步长时间(较低的值更好)— 作者

对于急切模型,获胜者是 flash-attn-3,其平均步长时间比我们的基准模型快了 54%。这相当于训练成本减少了 54%。在编译模式下,优化内核的性能大致相等,最快的实现达到了 202 毫秒,比基准实验提升了 20%。

如前所述,节省的精确影响在很大程度上取决于模型定义。为了评估这种变化性,我们使用修改后的设置重新进行了实验,将注意力序列长度增加到 3136 个标记。

IMG_SIZE = 224
BATCH_SIZE = 8

# Define ViT settings
NUM_HEADS = 12
HEAD_DIM = 64
DEPTH = 6
PATCH_SIZE = 4
SEQ_LEN = (IMG_SIZE // PATCH_SIZE)**2 # 3136

结果总结在下面的表格中:

大规模序列长度的结果(较低的值更好)— 作者

我们的初步观察是,当序列长度较大时,注意力内核的性能影响更为明显。再次,flash-attn-3 在急切执行模式下领先,这一次相比于 PyTorch 本地函数性能提升约 5 倍。对于编译后的模型,我们看到 TE 内核在整体最优步长时间 53 毫秒的表现下脱颖而出。

使用 FlexAttention 自定义注意力机制

到目前为止,我们主要关注了标准的注意力函数。然而,有时我们可能希望使用典型注意力计算的变体,其中我们要么屏蔽掉部分中间张量的值,要么对它们应用某些操作。这类变化可能会干扰我们使用前面介绍的优化注意力块的能力。在本节中,我们将讨论一些解决方法:

利用高级内核 API

许多优化过的注意力内核提供了广泛的 API,允许定制注意力计算。在实现新方案之前,请先探索这些 API,看看它们是否已经支持所需的功能。

实现自定义内核: 如果现有的 API 无法满足你的需求,可以考虑创建自己的自定义注意力实现。在之前的文章中(例如,这里)我们讨论了自定义内核开发的一些优缺点。实现最佳性能可能非常困难。如果你选择这条路径,一种方法可能是从现有的(最佳)内核开始,进行最小的修改以集成所需的变化。

使用 FlexAttention: PyTorch 最近新增了 FlexAttention,它使用户能够实现各种各样的注意力变体,而无需在性能上做出妥协。用 score 表示查询和键令牌的点积结果,flex_attention 允许编程一个 score_mod 函数或一个自动应用于 score 张量的 block_mask 屏蔽。请参见 文档 和附带的 attention-gym 仓库,了解 API 支持的操作类型示例。

FlexAttention 通过将 score_mod 操作符 编译 到注意力操作符中,从而创建一个单一的融合内核。它还利用 block_masks 的稀疏性来避免不必要的计算。FlexAttention 文档中报告的 基准测试 显示了在多种使用案例中取得了显著的性能提升。

让我们来看一下 score_modblock_mask 的实际应用。

Score Mod 示例 — 使用 Tanh 进行软限制

Soft-capping 是一种常用的技术,用于控制 logit 大小(例如,见 这里)。以下代码块扩展了我们的 PyTorch 原生注意力内核,添加了软限制:

def softcap_attn(q, k, v):
    scale = HEAD_DIM ** -0.5
    q = q * scale
    attn = q @ k.transpose(-2, -1)
    # apply soft-capping
    attn = 30 * torch.tanh(attn/30)
    attn = attn.softmax(dim=-1)
    x = attn @ v
    return x

在下面的代码块中,我们先用我们的 PyTorch 原生内核训练模型,然后使用优化过的 Flex Attention API。这些实验是在 3136 长度的序列设置下运行的。

# flex attention imports
from torch.nn.attention.flex_attention import (
    create_block_mask,
    create_mask,
    flex_attention
)
compiled_flex = torch.compile(flex_attention)

# score_mod definition
def tanh_softcap(score, b, h, q_idx, kv_idx):
    return 30 * torch.tanh(score/30)

block_fn = functools.partial(MyAttentionBlock, attn_fn=softcap_attn)

print(f'Attention with Softcap')
train(block_fn)
print(f'Compiled Attention with Softcap')
train_compile(block_fn)

flex_fn = functools.partial(flex_attention, score_mod=tanh_softcap)
compiled_flex_fn = functools.partial(compiled_flex, score_mod=tanh_softcap)

block_fn = functools.partial(MyAttentionBlock,
                             attn_fn=flex_fn)
compiled_block_fn = functools.partial(MyAttentionBlock,
                             attn_fn=compiled_flex_fn)

print(f'Flex Attention with Softcap')
train(compiled_block_fn)
print(f'Compiled Flex Attention with Softcap')
train_compile(block_fn)

实验结果记录在下表中:

Soft-cap 步骤时间结果(越低越好) — 作者

Flash Attention 核心的影响显而易见,在贪婪模式下带来了约 3.5 倍的性能提升,在编译模式下提升了 1.5 倍。

Mask Mod 示例 — 邻域屏蔽

我们通过对注意力得分应用稀疏掩码来评估mask_mod功能。回想一下,我们序列中的每个标记都代表我们二维输入图像中的一个补丁。我们修改了核函数,使得每个标记仅关注位于相应二维标记数组中 5x5 窗口内的其他标记。

# convert the token id to a 2d index
def seq_indx_to_2d(idx):
    n_row_patches = IMG_SIZE // PATCH_SIZE
    r_ind = idx // n_row_patches
    c_ind = idx % n_row_patches
    return r_ind, c_ind

# only attend to tokens in a 5x5 surrounding window in our 2D token array
def mask_mod(b, h, q_idx, kv_idx):
    q_r, q_c = seq_indx_to_2d(q_idx)
    kv_r, kv_c = seq_indx_to_2d(kv_idx)
    return torch.logical_and(torch.abs(q_r-kv_r)<5, torch.abs(q_c-kv_c)<5)

作为我们实验的基准,我们使用支持传递注意力掩码的 PyTorch SDPA。以下代码块包含了掩码 SDPA 实验以及 Flex Attention 实现:

# materialize the mask to use in SDPA
mask = create_mask(mask_mod, 1, 1, SEQ_LEN, SEQ_LEN, device='cuda')

set_sdpa_backend('all')
masked_sdpa = functools.partial(sdpa, attn_mask=mask)
block_fn = functools.partial(MyAttentionBlock,
                             attn_fn=masked_sdpa)
print(f'Masked SDPA Attention')
train(block_fn)
print(f'Compiled Masked SDPA Attention')
train_compile(block_fn)

block_mask = create_block_mask(mask_mod, None, None, SEQ_LEN, SEQ_LEN)
flex_fn = functools.partial(flex_attention, block_mask=block_mask)
compiled_flex_fn = functools.partial(compiled_flex, block_mask=block_mask)

block_fn = functools.partial(MyAttentionBlock,
                             attn_fn=flex_fn)
compiled_block_fn = functools.partial(MyAttentionBlock,
                             attn_fn=compiled_flex_fn)

print(f'Masked Flex Attention')
train(compiled_block_fn)
print(f'Compiled Masked Flex Attention')
train_compile(block_fn)

实验结果如下所示:

掩码注意力步骤时间结果(时间越短越好)——由作者提供

再次证明,Flex Attention 提供了显著的性能提升,急切模式下提升了 2.19 倍,编译模式下提升了 2.59 倍。

Flex Attention 的局限性

尽管我们成功展示了 Flex Attention 的强大潜力,但仍有一些局限性需要注意:

  1. 修改范围有限:使用 Flex Attention(截至本文写作时)只能修改注意力得分(即查询和键标记之间的点积结果)。它不支持在注意力计算的其他阶段进行修改。

  2. 依赖于 torch.compile: 鉴于对 torch.compile 的依赖,必须小心避免过多的重新编译,因为这可能会显著降低运行时性能。例如,虽然文档掩码的支持非常有吸引力,但只有当所有文档的长度之和保持固定时,才能按预期执行。

  3. 不支持score_mod中的可训练参数: 在撰写本文时,Flex Attention 不支持包含可训练参数的score_mod实现。例如,尽管文档中提到支持相对位置编码,但这些通常是通过可训练参数(而非固定值)实现的,而目前无法支持这一点。

面对这些限制,我们可以回到前面讨论的其他优化机会之一。

总结

随着机器学习模型中对变换器架构和注意力层的依赖增加,优化这些组件的工具和技术的需求也随之增加。在这篇文章中,我们探讨了多种注意力核函数变体,每种变体都有其独特的属性、能力和局限性。重要的是,一种方法并不适用于所有情况——不同的模型和应用场景需要使用不同的核函数和不同的优化策略。这强调了拥有各种各样的工具和技术以优化注意力层的重要性。

在这篇续篇中,我们将进一步探讨注意力层优化,重点应用我们讨论过的一些工具来应对处理可变长度输入序列的挑战。敬请期待…

一目了然:你的图表差吗?

原文:towardsdatascience.com/information-at-a-glance-do-your-charts-suck-8b4167a18b88?source=collection_archive---------7-----------------------#2024-12-04

前注意处理、格式塔理论和视觉数据编码如何影响数据设计决策

詹姆斯·威尔金斯Towards Data Science 詹姆斯·威尔金斯

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

--

图像由 DALL-E 生成,OpenAI

让我们面对现实:你做的那份报告——实际上没有人会真心去读它。

在最佳情况下,人们可能会浏览一遍,短暂停留在明亮的图表上。

但如果你设计的图表得当,一瞥就足以让人理解数据所表达的内容——至少在高层次上是这样。

快速传达信息的能力是将一张普通图表与优秀图表区分开来的关键。让我们来看看一些心理学技巧,看看我们如何能让图表更容易解读。

前注意特征

前注意特征是图表设计中可以无需专门关注即可感知的元素。

它们是我们在首次查看某物时立即吸引注意力的特征。

噪声中的信息

原文:towardsdatascience.com/information-in-noise-08bc05bf6484?source=collection_archive---------5-----------------------#2024-09-03

一次可视化多个时间序列的两种技巧

Lenix CarterTowards Data Science Lenix Carter

·发表于 Towards Data Science ·阅读时长 4 分钟 ·2024 年 9 月 3 日

--

想象一下:你手头有一堆折线图,而且你有信心在这些数据中至少隐藏着一个趋势。无论你是在追踪公司成千上万种产品的销售数据,还是深入分析股市数据,你的目标是揭示这些子趋势,并使它们在你的可视化中脱颖而出。让我们探索几种帮助你实现这一目标的技巧。

绘制了数百条线,但不清楚子趋势是什么。这些合成数据可以展示这些策略的好处。(图片来源:作者)

选项 1 — 密度折线图:

密度折线图是一种巧妙的绘图技巧,由 Dominik Moritz 和 Danyel Fisher 在他们的论文中提出,Visualizing a Million Time Series with the Density Line Chart 这种方法将众多折线图转化为热力图,揭示了线条重叠最多的区域。

当我们将密度折线图应用到我们之前展示的合成数据时,结果如下所示:

PyDLC 使我们能够看到线条高度重叠的“热点”区域。(图片来源:作者)

这种实现使我们能够看到趋势出现的位置,并识别出使这些数据变得有趣的子趋势。

在这个例子中,我们使用了 Charles L. Bérubé 提供的 Python 库 PyDLC。由于该库的用户友好设计,实施起来非常简单。

plt.figure(figsize=(14, 14))
im = dense_lines(synth_df.to_numpy().T, 
                 x=synth_df.index.astype('int64'), 
                 cmap='viridis',
                 ny=100,
                 y_pad=0.01
                )

plt.ylim(-25, 25)

plt.axhline(y=0, color='white', linestyle=':')

plt.show()

使用密度折线图时,请记住,像 nyy_pad 这样的参数可能需要调整才能获得最佳结果。

选项 2 — 线条的密度图:

这种技术并未得到广泛讨论,也没有一个公认的名称。然而,它本质上是“折线密度图”或“折线密度可视化”的一种变体,我们通过使用较粗且低透明度的线条来揭示重叠区域和密集区。

这种技术非常适合展示子趋势,并且能减少多条折线带来的认知负担。 (图片来自作者)

我们可以清晰地识别出似乎存在的两种不同趋势,并观察到在正弦波下行运动期间,它们有高度的重叠。然而,要准确指出哪里效果最强,还是有些困难。

这种方法的代码也非常简单:

plt.figure(figsize=(14, 14))

for column in synth_df.columns:
    plt.plot(synth_df.index, 
             synth_df[column], 
             alpha=0.1, 
             linewidth=2, 
             label=ticker,
             color='black'
            )

在这里,可能需要调整的两个参数是alphalinewidth

一个例子

假设我们正在寻找 50 只股票的日收益率中的子趋势。第一步是提取数据并计算日收益率。

import yfinance as yf
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

stock_tickers = [
    'AAPL', 'MSFT', 'GOOGL', 'AMZN', 'TSLA', 'META', 'NVDA', 'BRK-B', 'UNH', 'V',
    'HD', 'MA', 'KO', 'DIS', 'PFE', 'NKE', 'ADBE', 'CMCSA', 'NFLX', 'CSCO',
    'INTC', 'AMGN', 'COST', 'PEP', 'TMO', 'AVGO', 'QCOM', 'TXN', 'ABT', 'ORCL',
    'MCD', 'MDT', 'CRM', 'UPS', 'WMT', 'BMY', 'GILD', 'BA', 'SBUX', 'IBM',
    'MRK', 'WBA', 'CAT', 'CVX', 'T', 'MS', 'LMT', 'GS', 'WFC', 'HON'
]

start_date = '2024-03-01'
end_date = '2024-09-01'

percent_returns_df = pd.DataFrame()

for ticker in stock_tickers:
    stock_data = yf.download(ticker, start=start_date, end=end_date)

    stock_data = stock_data.fillna(method='ffill').fillna(method='bfill')

    if len(stock_data) >= 2:
        stock_data['Percent Daily Return'] = stock_data['Close'].pct_change() * 100

        stock_data['Ticker'] = ticker
        percent_returns_df = pd.concat([percent_returns_df, stock_data[['Ticker', 'Percent Daily Return']]], axis=0)

percent_returns_df.reset_index(inplace=True)

display(percent_returns_df)

然后我们可以绘制数据。

pivot_df = percent_returns_df.pivot(index='Date', columns='Ticker', values='Percent Daily Return')

pivot_df = pivot_df.fillna(method='ffill').fillna(method='bfill')

plt.figure(figsize=(14, 14))
sns.lineplot(data=pivot_df, dashes=False)
plt.title('Percent Daily Returns of Top 50 Stocks')
plt.xlabel('Date')
plt.ylabel('Percent Daily Return')
plt.legend(title='Stock Ticker', bbox_to_anchor=(1.05, 1), loc='upper left')
plt.grid(True)
plt.tight_layout()

一个非常杂乱的多条折线图,几乎没有明显的信息。 (图片来自作者)

密度折线图由于数据的零散性,确实面临一些挑战。然而,它仍然能提供关于整体市场趋势的有价值的见解。例如,你可以观察到密度最高的区域对应于显著的市场下跌,突出显示了市场的低迷时期。

(图片来自作者)

plt.figure(figsize=(14, 14))
im = dense_lines(pivot_df[stock_tickers].to_numpy().T, 
                 x=pivot_df.index.astype('int64'), 
                 cmap='viridis',
                 ny=200,
                 y_pad=0.1
                )

plt.axhline(y=0, color='white', linestyle=':')
plt.ylim(-10, 10)

plt.show()

然而,我们发现透明度技术在这个特定问题上表现得明显更好。我们之前提到的市场下跌变得更加清晰易见。

(图片来自作者)

plt.figure(figsize=(14, 14))

for ticker in pivot_df.columns:
    plt.plot(pivot_df.index, 
             pivot_df[ticker], 
             alpha=0.1, 
             linewidth=4, 
             label=ticker,
             color='black'
            )

结论

两种策略各有其优点和强项,哪种方法最适合你的工作可能在你尝试过两者之后才会变得明显。我希望这些技巧能对你未来的项目有所帮助。如果你有其他处理大量折线图的技巧或应用案例,我很想听听你的分享!

感谢阅读,保重。

大型组织中的数据调和难题

原文:towardsdatascience.com/information-rationalization-in-large-organizations-a-unique-use-case-for-clustering-techniques-5aea5bc6ebd4?source=collection_archive---------11-----------------------#2024-04-18

我们如何利用聚类技术来整合和重构大量不相干的仪表板?

Ramkumar KTowards Data Science Ramkumar K

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

--

图片由Luke Chesser提供,来源于Unsplash

背景

组织每天都会生成大量数据。仪表板被用来分析这些数据并推导出有意义的商业洞察,以及跟踪关键绩效指标(KPI)。随着时间的推移,我们会积累数百个(或更多)这样的仪表板。通常,这种仪表板激增是由多个小组在没有对已有分析结果有可视化了解的情况下,独立开发各自的分析工具所推动的。例如,销售团队可能会创建一个仪表板来跟踪其预测与实际销售情况,而不知道供应链团队已经有一个用于跟踪预测准确性的仪表板。这样不仅导致了重复劳动,而且我们可能会在多个仪表板中看到相同的指标和分析结果,却没有一个统一的数据源。

当团队汇报业务状态给高层管理时,他们通常需要花费数小时来调和不同的指标,以便向领导层提供清晰和连贯的信息。假设我们有一个 1000 人的组织,其中每个人每周平均需要花费 2 小时来进行此类工作……

检查神经网络模型在边缘部署中的表现

原文:towardsdatascience.com/inspecting-neural-network-model-performance-for-edge-deployment-d4f8f18dbfd5?source=collection_archive---------11-----------------------#2024-01-05

详细探讨量化 CNN 和基于变换器的模型以及评估和理解它们在边缘硬件上效果的技术

Lindo St. AngelTowards Data Science Lindo St. Angel

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

--

图片由 Gavin Allanwood 提供,来源:Unsplash

引言

本文将向你展示如何将神经网络模型进行转换和量化,以便在边缘设备上进行推理,并且如何检查它们的量化效果,了解运行时延迟和模型内存使用情况,从而优化性能。尽管本文主要集中在使用卷积神经网络(CNN)和基于变换器的神经网络解决非侵入式负载监测(NILM)问题,作为展示这些技巧的方式,但你也可以使用该通用方法来训练、量化和分析模型,从而解决其他问题。

NILM 的目标是从总电源信号中恢复各个电器的能耗,该信号反映了建筑或家庭的总电力消耗。NILM 也被称为能量分解,你可以交替使用这两个术语。

你可以在我的 GitHub 上找到用于生成本文所示结果的代码,链接为 Energy Management Using Real-Time Non-Intrusive Load Monitoring,以及为简便起见此处省略的其他细节。

非侵入式负载监测(NILM)算法选择与模型

算法选择

能源拆分是一个高度欠定且单通道的盲源分离(BSS)问题,这使得获得准确预测变得具有挑战性。设 M 为家电的数量,i 为指代第 i 个家电的索引。给定时间 t 时的总功率消耗 x 是所有家电 M 的功率消耗之和,记为 yᵢ,对于所有 {i=1,…,M}。因此,给定时间 t 时的总功率消耗 x 可以通过方程 1 表达,其中 e 是噪声项。

目标是求解逆问题,并在给定总功率信号 x 的情况下估计家电功率消耗 yᵢ,并以适合在边缘部署的方式进行估计。

你可以通过使用序列到点(seq2point)学习与神经网络来解决单通道 BSS 问题,并且可以通过使用变换器(transformers)、卷积神经网络(CNN)和循环神经网络来应用于 NILM 问题。序列到点学习涉及训练一个神经网络,将输入时间序列(例如,在 NILM 的情况下为总功率读数)与输出信号之间进行映射。你使用一个滑动输入窗口来训练网络,该网络会在窗口的中点生成一个对应的单点输出。

我选择了 seq2point 学习方法,我的实现受到了 Michele D’Incecco 等人¹和 Zhenrui Yue 等人²的工作的启发和指导。我开发了各种 seq2point 学习模型,但将重点放在基于变换器和 CNN 架构的模型上。

神经网络模型

你可以在图 1 中看到 CNN 模型,该模型适用于长度为 599 样本的输入序列。你可以在这里查看完整的模型代码。该模型遵循传统的 CNN 概念,源自计算机视觉领域的应用,其中多个卷积层从输入的功率序列中提取特征,随着输入在网络中传递,细节逐渐变得更精细。这些特征是家电的开关模式和功率消耗水平。最大池化操作在每个卷积层后管理模型的复杂性。最后,密集层输出窗口的最终单点功率消耗估计值,该值在用于下游处理之前会被去归一化。该模型使用默认值时有大约 4000 万个参数。

图 1 — CNN 模型

你可以在图 2 中看到输入序列长度为 599 个样本的变换器模型,其中变换器模块是 BERT 风格的编码器。你可以在这里查看完整代码。输入序列首先通过卷积层扩展到潜在空间,类似于 CNN 模型中的特征提取。池化和 L2 归一化减少了模型复杂性并缓解了异常值的影响。接着,BERT 风格的变换器队列处理潜在空间序列,包括位置嵌入和应用重要性加权的变换器模块。多个层级处理变换器模块的输出,包括相对位置嵌入(使用围绕信号中点的对称权重)、平均池化(将序列简化为每个特征的单一值),最后是密集层,输出窗口的最终单一估算功率值,然后进行去归一化以便后续处理。使用默认值时,该模型有约 160 万个参数。

图 2 — 整体变换器模型

你可以在下图的图 3 中看到 BERT 风格的变换器编码器。

图 3 — 变换器编码器。

NILM 数据集

为了解决 NILM 问题,捕获了多个大型公开可用的数据集,这些数据集专门设计用于此目的,数据采集地点为来自不同国家的家庭建筑。这些数据集通常包含数千万个有功功率、无功功率、电流和电压的样本,但采样频率各异,因此需要在使用前对数据进行预处理。大多数 NILM 算法仅使用真实(有功或真实)功率数据。能源分解研究通常考虑五种家电:水壶、微波炉、冰箱、洗碗机和洗衣机。这些就是我在本文中使用的家电,我主要关注的是REFIT³ 数据集。

请注意,这些数据集通常非常不平衡,因为大部分时间,家电处于关闭状态。

模型训练与结果

我使用了TensorFlow来训练和测试模型。你可以在这里找到与本节相关的代码。我对各个家电的 seq2point 学习模型进行了训练,使用的是经过 z-score 标准化的 REFIT 数据,或标准化至[0, Pₘ],其中 Pₘ 是家电在其活动状态下的最大功率消耗。标准化数据往往能带来最佳的模型性能,因此我默认使用了它。

我使用了以下度量标准来评估模型的性能。你可以在这里查看计算这些度量标准的代码。

  • 平均绝对误差(MAE)评估预测值与真实功率之间的绝对差异,并计算其平均值,按照以下公式定义。

  • 标准化信号聚合误差(SAE)表示总能量的相对误差。设 r 为家电的总能耗,rₚ 为预测的总能耗,则 SAE 按以下公式定义。

  • 每日能量(EpD)衡量一天内预测的能耗,对于家庭用户关注一段时间内的总能耗时非常有价值。设 D 为总天数,e 为每日家电能耗,则 EpD 按以下公式定义。

  • 标准化去聚合误差(NDE)衡量预测值与家电真实功率之间平方差的标准化误差,按以下公式定义。

  • 我还使用了准确率(ACC)、F1 分数(F1)和 Matthew’s 相关系数(MCC)来评估模型是否能够在用于训练和测试模型的严重不平衡数据集上表现良好。这些指标依赖于家电设备的开关状态的计算结果。ACC 等于在测试数据集上正确预测的时间点数。以下公式定义了 F1MCC,其中 TP 代表真正例,TN 代表真反例,FP 代表假正例,FN 代表假反例。

MAE, SAE, NDEEpDₑ 定义为 100% 乘以(预测的 EpD 减去真实值的 EpD)再除以真实值的 EpD,反映了模型正确预测家电能耗水平的能力。F1MCC 表示模型在使用不平衡类别时正确预测家电开关状态的能力。在此应用中,ACC 的价值不如 F1MCC,因为大多数情况下,模型会准确预测占主导地位的家电(在数据集中占比最大的家电)处于关闭状态。

我使用了一个包含 599 个样本的滑动窗口,这些样本是聚合的实际功率消耗信号,作为 seq2point 模型的输入。我使用对应家电窗口的中点作为目标。您可以通过 window_generator.py 模块中定义的 WindowGenerator 类的实例查看生成这些样本和目标的代码。

你可以在train.py中看到我用来训练模型的代码,它使用了tf.distribute.MirroredStrategy分布式训练策略。我使用了 Keras Adam 优化器,并且通过提前停止来减少过拟合。

训练和优化器的关键超参数总结如下:

  • 输入窗口大小:599 个样本

  • 全局批次大小:1024 个样本。

  • 学习率:1e-04

  • Adam 优化器:beta_1=0.9, beta_2=0.999, epsilon=1e-08

  • 提前停止准则:6 个 epoch。

我使用下面的损失函数计算训练梯度,并在每个批次上评估验证损失。该损失函数结合了均方误差、二元交叉熵和均值绝对误差,且在分布式模型副本批次上求平均。

其中 x, x_hat ∈ [0, 1]表示实际功率使用值和预测功率使用值,二者除以每个电器的最大功率限制;s, s_hat ∈ {0, 1}是电器状态标签和预测值。绝对误差项仅在状态标签为开启时,或者预测错误时才应用。超参数 lambda 调节每个电器的绝对损失项。

你可以在下表中看到 CNN 模型的典型性能指标。

表 1 — CNN 模型性能

你可以在下表中看到变压器模型的典型性能指标。

表 2 — 变压器模型性能

你可以看到,尽管变压器模型的参数比 CNN 模型少约 26 倍,但两者的性能相似。然而,由于变压器模型使用了自注意力机制,其计算复杂度为 O(n²),而 CNN 模型的复杂度为 O(n),其中n是输入序列的长度,因此每一步变压器的训练时间大约是 CNN 的七倍。根据训练(和推理)效率,你可以看到 CNN 在模型性能损失很小的情况下更为可取。

模型转换与量化

将浮点数形式的模型图转换为适用于边缘硬件推理的形式,包括基于 CPU、MCU 和专门优化 int8 操作的计算硬件,所涉及的步骤如下:

  • 使用 Nvidia GPU 以 float32 或类似 TensorFloat-32 的表示法训练模型。输出将是一个完整的网络图;我使用了 TensorFlow 的 SavedModel 格式,它是一个完整的 TensorFlow 程序,包括变量和计算。

  • 使用 TensorFlow Lite 或等效工具将浮点图转换为优化后的边缘硬件格式。输出将是一个可以在 CPU 上运行的平面文件,但所有操作仍将以 float32 进行。请注意,并非所有 TensorFlow 操作符都可以转换为 TFLite 等效操作符。大多数在 CNN 网络中使用的层和操作符都可以转换,但我在设计 Transformer 网络时小心避免了 TFLite 转换问题。参见TensorFlow Lite 和 TensorFlow 操作符兼容性

  • 量化并优化转换后模型的权重、偏差和激活值。我使用了不同的量化模式,将模型部分或完全量化为 int8、int16 或它们的组合,导致在目标硬件上的推理延迟不同。

我使用TensorFlow Lite (TFLite)转换器 AP在 CNN 和 Transformer 模型上执行了训练后量化,并采用了不同的量化模式,以提高在边缘硬件(包括树莓派和 Google Edge TPU)上的推理速度,同时管理对准确性的影响。您可以在下面看到我使用的量化模式。

  • convert_only:转换为 tflite 格式,但保持所有参数为 Float32(不进行量化)。

  • w8:将权重从 float32 量化为 int8,偏差量化为 int64。激活值保留为 Float32。

  • w8_a8_fallback:与 w8 相同,但将激活值从 float32 量化为 int8。如果某个操作符没有整数实现,则回退为 float。

  • w8_a8:与 w8 相同,但将激活值从 float32 量化为 int8。对所有操作符强制执行完整的 int8 量化。

  • w8_a16:与 w8 相同,但将激活值量化为 int16。

CNN 模型使用所有模式进行了量化,以了解延迟和准确性之间的最佳折衷。只有 Transformer 模型的权重使用 w8 模式量化为 int8;激活值需要保持为 float32,以保持可接受的准确性。请参见convert_keras_to_tflite.py了解执行此量化的代码,该代码还使用TensorFlow Lite 的量化调试器检查模型中每一层的量化效果。我使用TensorFlow Lite 模型基准工具对转换后的模型进行了性能分析,以量化推理延迟。

完全量化模型需要通过代表实际数据的训练和测试数据集来校准模型的激活。对于高度不平衡的数据,校准可能具有挑战性,因为随机选择样本可能会导致较差的校准和量化模型精度。为了解决这个问题,我使用了一种算法构建了一个平衡的家电开关状态代表数据集。你可以在这里找到该代码,并在下面的代码片段中查看。

图 4 — 代表性生成器代码片段

你可以在下表中找到量化推理结果,其中 Lx86 是使用八个 TFlite 解释器线程的 3.8 GHz x86 机器的平均推理延迟,Larm 是使用四个线程的 ARM aarch-64 架构 Raspberry Pi 4 的平均推理延迟,两个计算机都使用 TensorFlow Lite 的XNNPACK CPU 委托。Ltpu 是 Google Coral Edge TPU 上的平均推理延迟。我保持了 x86 和 ARM 架构机器上的模型输入输出为 float32,以最大化推理速度。对于边缘 TPU,我将它们设置为 int8。

CNN 模型结果与讨论

你可以在下表中看到量化模式 w8 下 CNN 模型的量化结果。

表 4 — 模式 w8 的量化 CNN 模型

以下展示了 CNN 水壶模型在其他量化模式下的量化结果。你可以看到,边缘 TPU 的延迟远高于其他机器。因此,我将分析重点放在了 x86 和 ARM 架构上。

表 5 — 其他模式下的量化 CNN 水壶模型

其他家电模型的结果由于篇幅问题已省略,但它们在量化模式下呈现出相似的特性。

你可以看到激活量化的负面影响,但由于正则化效应,权重量化对某些模型性能指标有适度的益处。如预期,完全量化模式导致最低的延迟。通过 w8_a16 模式将激活量化为 int16 会导致最高的延迟,因为目前在 TensorFlow Lite 中仅提供非优化的参考内核实现,但该方案在权重量化带来的正则化效益和更好地保留激活数值方面,能实现最佳的模型指标。

你还可以看到,x86 机器的推理延迟模式为 w8 > convert_only > w8_a8,而 aarch64 机器则为 convert_only > w8 > w8_a8,尽管对于 x86 的变化更为显著。为了更好地理解这一点,我使用 TFLite 模型基准工具对转换后的模型进行了性能分析。下文展示了代表其他模型的 CNN 微波模型的性能分析结果摘要。

  1. 在 x86 上的模型性能分析(从最慢到最快)

可以看到,完全连接和卷积操作在所有情况下都需要最长的执行时间,但在 w8_a8 完全量化模式下,它们的速度要快得多。

表 6 — CNN x86 模型在 w8 模式下的性能分析

表 7 — CNN x86 模型在 convert_only 模式下的性能分析

表 8 — CNN x86 模型在 w8_a8 模式下的性能分析

  1. 在 aarch64 上的模型性能分析(从最慢到最快)

复制和最大池化操作在 x86 上比在 aarch64 上慢,可能是由于内存带宽和微架构差异造成的。

表 9 — CNN aarch64 模型在 convert_only 模式下的性能分析

表 10 — CNN aarch64 模型在 w8 模式下的性能分析

表 11 — CNN aarch64 模型在 w8_a8 模式下的性能分析

  1. 量化效能

当量化分布与原始浮动分布相似时,RMSE / scale 指标接近 1 / sqrt(12)(约为 0.289),这表示模型量化效果良好。值越大,说明该层可能没有得到很好的量化。以下表格显示了 CNN kettle 模型的 RMSE / Scale 指标,"Suspected?" 列表示那些显著超过 0.289 的层。其他模型为了简洁起见省略,但结果类似。这些层可以保留为浮动类型,以生成一个选择性量化的模型,从而提高准确性,代价是牺牲推理性能。然而,在 CNN 模型中这样做并没有实质性地提高准确性。参见 使用量化调试器检查量化误差

你可以在下面找到使用 w8_a8 模式的 CNN kettle 模型的层量化效能指标。

表 12 — 使用 w8_a8 模式的 CNN kettle 模型的层量化效能指标

  1. 模型内存占用

我使用了TFLite 模型基准工具,获得了 TFLite CNN 微波模型在运行时的近似 RAM 消耗情况,结果如表格所示,分别列出了每种量化模式下的 RAM 消耗和 TFLite 模型的磁盘空间。其他 CNN 模型表现出类似的特性。对于 x86 架构的结果与 arm 架构相同。注意,Keras 模型在磁盘上消耗约 42.49MB。可以看到,由于 float32 到 int8 的权重转换,磁盘存储空间大约减少了四倍。

有趣的是,RAM 的运行时使用情况因 TFLite 算法优化中间张量的使用而大幅变化。这些张量是预先分配的,以减少推理延迟,但会以牺牲内存空间为代价。请参见优化 TensorFlow Lite 运行时内存

表 13 — CNN 模型的内存使用情况

变压器模型的结果与讨论

尽管我在变压器模型推理评估时启用了 XNNPACK 委托,但由于变压器模型包含动态张量,因此没有加速。在使用 TFLite 解释器进行推理时,我遇到了以下警告:

尝试在包含动态大小张量的图形中使用仅支持静态大小张量的委托(tensor#94 是一个动态大小的张量)。

这个警告意味着 XNNPACK 不支持所有操作符,将回退到默认的 CPU 内核实现。未来的工作将涉及重构变压器模型,仅使用静态大小的张量。请注意,当 TFLite 运行时遇到控制流操作(例如 if、while)时,张量可能会被标记为动态。换句话说,即使模型图中没有任何动态形状的张量,模型在运行时仍可能包含动态张量。目前的变压器模型使用了if控制流操作。

你可以在下表中看到变压器模型在量化模式 w8 下的量化结果。

表 14 — 变压器模型在量化模式 w8 下的量化结果

变压器壶和微波模型在量化模式 convert_only 下的量化结果如表格所示。

表 15 — 变压器壶和微波模型在量化模式 convert_only 下的量化结果

  1. x86 模型性能分析(从最慢到最快)

在 w8 模式下,完全连接层占据了主要计算负载,但在 convert_only 模式下较少。这种行为可能与 x86 内存微架构对 int8 权重的处理有关。

表 16 — x86 变压器模型在 w8 模式下的性能分析

表 17 — x86 变压器模型剖析(模式:convert_only)

2. 在 aarch64 上的模型剖析(从最慢到最快)

你可以看到,在 w8 模式下,ARM 架构似乎比 x86 架构更高效地计算完全连接层(FULLY_CONNECTED)。

表 18 — aarch64 变压器模型剖析(模式:convert_only)

表 19 — aarch64 变压器模型剖析(模式:w8)

3. 量化效能

你可以在这里找到使用 w8_a8 模式的变压器 kettle 模型的层量化效能指标,尽管如上所述,量化变压器模型的激活值会导致模型性能下降。你可以看到,RSQRT 运算符,尤其不适合量化;这些运算符在高斯误差线性激活函数中使用,帮助解释了模型性能差的原因。其他变压器设备模型显示出类似的效能指标。

4. 模型内存占用

与 CNN 情况相同,我使用了TFLite 模型基准测试工具来获取 TFLite 微波模型在运行时的近似 RAM 消耗,下面的表格展示了每种相关量化模式和 TFLite 模型的磁盘空间。其他变压器模型显示了类似的特性。请注意,Keras 模型在磁盘上大约消耗 6.02MB。你可以看到,由于权重从 float32 量化到 int8,模型大小减少了大约三倍,这比 CNN 案例中看到的四倍减少要小,可能是因为变压器模型中的层数较少。你还可以看到,x86 的 TFLite 运行时比 aarch64 架构更节省内存。

表 20 — 变压器模型的磁盘和内存使用情况

结论

你可以使用 TensorFlow 和 TensorFlow Lite 在边缘有效地开发和部署模型。TensorFlow Lite 提供了有助于生产中理解和修改模型行为的工具,包括层量化检查和运行时剖析。

与变压器模型所用的典型运算符相比,CNN 模型中使用的运算符支持更好。你应该仔细选择如何设计你的网络,考虑这些约束,并在开发和训练模型之前,运行一个完整的端到端训练-转换-量化周期。

后训练量化非常适合对 CNN 网络进行完全量化,但我只能量化变压器网络的权重,以保持可接受的性能。为了更好的整数性能,应该使用量化感知方法训练变压器网络。

本文中用于解决 NILM 问题的 CNN 模型比它们的变换器模型大得多,但由于具有线性复杂性,训练速度更快,延迟更低。如果磁盘空间和 RAM 不是主要限制因素,CNN 模型是更好的解决方案。

参考文献

  1. arXiv:1902.08835 | Michele D'Incecco、Stefano Squartini 和 Mingjun Zhong 的《用于非侵入性负载监测的迁移学习》。

  2. BERT4NILM:Zhenrui Yue 等人提出的用于非侵入性负载监测的双向变换器模型。

  3. 可在知识共享署名 4.0 国际公共许可证下使用。

使用 LangChain 和 Chainlit 集成外部 API 与聊天机器人应用程序

原文:towardsdatascience.com/integrating-an-external-api-with-a-chatbot-application-using-langchain-and-chainlit-b687bb1efe58?source=collection_archive---------1-----------------------#2024-02-18

使用 LangChain 和 Chainlit 集成外部 API 以实现与聊天机器人应用程序的高级交互的实用指南

Tahreem RasulTowards Data Science Tahreem Rasul

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

--

在本教程中,我们将展示如何将外部 API 与自定义聊天机器人应用程序集成。在我之前关于构建自定义聊天机器人应用程序的文章中,我们介绍了如何使用LangChainOpenAI创建具有特定功能的聊天机器人,以及如何使用Chainlit为我们的聊天机器人构建 Web 应用程序。

API 集成聊天机器人的工作流程 — 作者提供的图像

如果你是这个系列的新读者,我建议查看我之前的文章,以获取详细的逐步指南:

[## 使用 Chainlit 和 LangChain 构建聊天机器人应用程序

在本文中,我们将使用 Chainlit 框架为我们的自定义聊天机器人 Scoopsie 开发应用程序界面…

medium.com](https://medium.com/@tahreemrasul/building-a-chatbot-application-with-chainlit-and-langchain-3e86da0099a6?source=post_page-----b687bb1efe58--------------------------------) [## 如何使用 LangChain 和 OpenAI 构建自己的聊天机器人

在本教程中,我们将使用 LangChain 和 OpenAI 构建一个针对特定用例的聊天机器人。如果你…

medium.com](https://medium.com/@tahreemrasul/how-to-build-your-own-chatbot-with-langchain-and-openai-f092822b6ba6?source=post_page-----b687bb1efe58--------------------------------)

将 LLM 代理与 LangChain 集成到 VICA 中

原文:towardsdatascience.com/integrating-llm-agents-with-langchain-into-vica-d18a5c8583c6?source=collection_archive---------7-----------------------#2024-08-20

了解我们如何使用 LLM 代理来改进和定制聊天机器人的交易过程!

Ng Wei ChengTowards Data Science Ng Wei Cheng

·发布于Towards Data Science ·阅读时长:14 分钟·2024 年 8 月 20 日

--

贡献者: Nicole Ren (GovTech), Ng Wei Cheng (GovTech)

VICA 标志,图片由作者提供

VICA(虚拟智能聊天助手)是 GovTech 的虚拟助手平台,利用人工智能(AI)使用户能够在其网站上创建、训练和部署聊天机器人。截止目前,VICA 支持超过 100 个聊天机器人,每月处理超过 70 万个用户查询。

在后台,VICA 的自然语言处理(NLP)引擎使用了从传统的意图匹配系统到生成式 AI 框架(如检索增强生成(RAG))等各种技术和框架。通过保持与最前沿技术的同步,我们的引擎在不断发展,确保每个公民的查询都能匹配到最佳答案。

除了简单的问答(Q&A)功能,VICA 旨在通过对话交易来增强聊天机器人。我们的目标是告别聊天机器人中机械而笨拙的表单体验,迎接具有类人辅助的个性化对话。

本文是两篇系列文章中的第一篇,旨在分享我们在 VICA 中构建的生成式 AI 解决方案。在本文中,我们将重点讨论 LLM 代理如何通过使用 LangChain 的 Agent 框架来帮助改进聊天机器人中的交易过程。

目录

  1. 简介

  2. 关于 LangChain 的所有信息

  3. LangChain 在生产环境中的应用

  4. LangChain 上线挑战

  5. LLM 代理的应用案例

  6. 结论

  7. 了解更多关于 VICA 的信息

  8. 致谢

  9. 参考文献

介绍

示例交易聊天机器人对话,图像由作者提供

基于交易的聊天机器人是旨在为用户促进并执行特定交易的对话代理。这些聊天机器人超越了简单的问答交互,允许用户直接在聊天机器人界面中完成诸如预订、购买或表单提交等任务。

为了执行交易,聊天机器人需要在后端进行定制,以处理额外的用户流程并进行 API 调用。

随着大规模语言模型(LLMs)的兴起,它为简化和增强聊天机器人功能的开发开辟了新的途径。LLMs 能显著提升聊天机器人理解并响应各种查询的能力,从而更有效地管理复杂的交易。

尽管已经存在意图匹配聊天机器人系统,能够引导用户通过预定义的交易流程,但 LLM 提供了显著的优势,通过在多轮交互中保持上下文并处理各种输入和语言变化。此前的交互常常显得尴尬和生硬,因为用户必须从预设的卡片中选择选项或输入特定的短语才能触发交易流程。例如,从“我可以付款吗?”到“请让我支付”这种微小的变化可能会导致交易流程无法触发。相反,LLMs 可以适应各种沟通风格,允许它们解读那些不完全符合预定义意图的用户输入。

认识到这一潜力后,我们的团队决定利用 LLM 进行交易处理,通过拆解并理解用户的意图,使用户能够以更自然、灵活的方式进入交易流程。鉴于 LangChain 提供了一个实现代理工作流的框架,我们选择利用其代理框架创建一个智能系统来处理交易。

在本文中,我们还将分享我们开发的两个使用 LLM 代理的案例,即统计部门(DOS)统计表格生成器和自然对话设施预订聊天机器人。

关于 LangChain 的一切

在我们讨论如何使用 LLM 代理进行交易之前,我们首先会介绍什么是 LangChain,以及为什么我们选择尝试这个框架。

什么是 LangChain?

LangChain 是一个开源的 Python 框架,旨在帮助开发者构建利用 LLM 的 AI 驱动应用程序。

为什么使用 LangChain?

该框架通过提供抽象和模板,简化了开发过程,使得快速应用构建成为可能,节省了时间,并减少了我们的开发团队需要从零开始编写所有代码的需求。这使我们能够专注于更高层次的功能和业务逻辑,而不是低层次的编码细节。一个例子是,LangChain 如何帮助简化与 MongoDB、OpenAI 和 AWS 等流行服务提供商的第三方集成,促进了更快速的原型设计,并降低了集成各种服务的复杂性。这些抽象不仅加快了开发进程,还通过提供一致的结构提高了协作效率,使我们的团队能够高效地构建、测试和部署 AI 应用程序。

什么是 LangChain 的代理框架?

使用 LangChain 的主要特点之一是它们的代理框架。该框架允许管理与 LLM 和其他工具交互的智能代理,以执行复杂任务。

该框架的三个主要组件是

  • 代理

代理充当推理引擎,它们决定执行的适当动作及其顺序。它们利用 LLM 来为自己做出决策。代理有一个AgentExecutor,它调用代理并执行代理选择的工具。它还获取动作的输出并将其传递给代理,直到最终结果达成。

  • 工具

工具是代理可以使用的接口。为了创建一个工具,需要提供名称和描述。工具的名称和描述很重要,因为它们会被添加到代理的提示中。这意味着代理会根据提供的名称和描述来决定使用哪个工具。

链接是指一系列调用。链可以是编码步骤,或者只是对 LLM 或工具的调用。链可以根据 LangChain 提供的内容定制或直接使用。一个简单的链示例是LLMChain,这是一个对 LLM 进行查询的链。

LangChain 在生产环境中的应用

我们如何在 VICA 中使用 LangChain?

示例高层次微服务架构图,图片来源:作者

在 VICA 中,我们通过 REST API 设置了一个微服务来调用 LangChain。这有助于通过允许 VICA 的不同组件独立与 LangChain 通信,从而促进集成。因此,我们可以高效地构建我们的 LLM 代理,而不会受到系统其他组件变化或开发的影响。

LangChain 作为一个框架,在 LLM 领域相当广泛,涵盖了检索方法、代理和 LLM 评估。以下是我们在开发 LLM 代理时使用的组件。

ReAct 代理

在 VICA 中,我们使用了单一代理系统。该代理利用 ReAct 逻辑来决定执行的动作顺序(Yao 等,2022)。这种提示工程技术有助于生成以下内容:

  • 思考(在选择行动之前进行的推理)

  • 动作(执行的操作,通常是一个工具)

  • 动作输入(动作的输入)

  • 观察(来自工具输出的观察结果)

  • 最终答案(代理返回的生成性最终答案)

> Entering new AgentExecutor chain…
The user wants to know the weather today
Action: Weather Tool
Action Input: "Weather today"
Observation: Answer: "31 Degrees Celsius, Sunny"
Thought: I now know the final answer.
Final Answer: The weather today is sunny at 31 degrees celsius.
> Finished chain.

在上面的示例中,代理能够在选择使用的工具之前理解用户的意图。同时,生成了言语推理,帮助模型规划执行的行动顺序。如果观察结果不足以回答给定问题,代理可以转到不同的行动,从而更接近最终答案。

在 VICA 中,我们编辑了代理提示,使其更适合我们的用例。LangChain 提供的基础提示(链接在这里)通常对于大多数常见的用例已经足够,作为有效的起点。然而,可以通过修改它来增强性能并确保更高的相关性,以适应特定应用。这可以通过在将其作为参数传递给create_react_agent(根据你使用的 LangChain 版本可能有所不同)之前,使用自定义提示来实现。

为了确定我们的自定义提示是否有改进,我们采用了迭代式的提示工程方法:编写、评估和完善更多细节请见这里)。这一过程确保了提示能够在广泛的测试案例中有效地泛化。此外,我们还使用了 LangChain 提供的基础提示作为基准,评估我们的自定义提示,使我们能够在各种交易场景中根据不同的附加上下文评估其表现。

自定义工具与链式提示(Prompt Chaining)

对于本文中的两个自定义聊天机器人功能,我们利用了代理可以使用的自定义工具来执行交易。我们的自定义工具利用提示链式技术,在决定使用某一特定工具之前,先拆解并理解用户的请求。

提示链式(Prompt chaining)是一种技术,多个提示按顺序使用以处理复杂的任务或查询。它从初始提示开始,利用其输出作为后续提示的输入,允许进行迭代优化和上下文延续。这种方法增强了处理复杂查询的能力,提高了准确性,并通过逐步缩小焦点来保持连贯性。

对于每个交易用例,我们将过程拆解为多个步骤,这样可以在每个阶段为 LLM 提供更清晰的指令。这种方法通过使任务更具体、易于管理来提高准确性。我们还可以将本地化上下文注入到提示中,进一步明确目标并增强 LLM 的理解。根据 LLM 的推理,我们的自定义链会向外部 API 发出请求以获取数据,从而执行交易。

在每一步提示链条中,实现错误处理至关重要,因为 LLM 有时可能会产生幻觉或不准确的响应。通过引入错误处理机制,如验证检查,我们能够识别并解决输出中的不一致或错误。这使得我们能够为用户生成后备响应,解释 LLM 在推理过程中未能理解的部分。

最后,在我们的自定义工具中,我们避免直接使用 LLM 生成的输出作为最终响应,因为存在幻觉的风险。作为面对用户的聊天机器人,防止我们的聊天机器人传播任何误导性或不准确的信息至关重要。因此,我们确保所有对用户查询的响应都来源于通过我们的自定义链条提取的实际数据点。然后,我们将这些数据点格式化为预定义的响应,确保用户不会看到任何 LLM 直接生成的输出。

将 LangChain 投产的挑战

使用大语言模型(LLM)的挑战

挑战 #1:提示链条导致推理时间变慢

LLM 的一个挑战是推理时间。由于 LLM 拥有大量参数,并且需要反复调用以进行实时处理,因此其计算需求很高,导致推理时间相对较慢(每次提示需要几秒钟)。VICA 是一个每月接收 70 万次查询的聊天机器人。为了确保良好的用户体验,我们致力于在确保准确性的同时尽可能快速地提供回应。

提示链条增加了 LLM 输出的一致性、可控性和可靠性。然而,每增加一个链条就会显著降低我们的解决方案效率,因为这需要额外发起一次 LLM 请求。为了平衡简洁性和效率,我们对链条的数量设置了硬性限制,以防止用户等待时间过长。我们也选择不使用表现更好的 LLM 模型(如 GPT-4),因为它们的速度较慢,而选择了速度较快但通常表现良好的 LLM。

挑战 #2:幻觉

正如最近 Google 的功能“AI 概览”中所见,使用 LLM 生成输出可能导致不准确或非事实的细节。尽管对 LLM 进行基础知识绑定可以提高其一致性并减少幻觉的产生,但它并不能消除幻觉。

如上所述,我们利用提示链条将交易推理任务分解为更小、更易理解的任务。通过链式调用 LLM,我们能够提取处理复杂查询所需的信息。然而,对于最终的输出,我们从 LLM 执行的推理任务中构建了非生成性消息作为最终响应。这意味着在 VICA 中,我们的用户不会看到 LLM 代理生成的响应。

使用 LangChain 的挑战

挑战 #1:过度抽象

LangChain 的第一个问题是该框架抽象了太多细节,这使得很难针对特定的实际使用场景定制应用。

为了克服这些限制,我们不得不深入包内并自定义某些类,以更好地适应我们的应用场景。例如,我们修改了AgentExecutor类,将 ReAct 代理的操作输入路由到所选择的工具。这为我们的自定义工具提供了额外的上下文,有助于从用户查询中提取信息。

挑战 #2:文档的缺乏

第二个问题是文档的缺乏以及框架的不断演进。这使得开发变得困难,因为需要通过查看包的代码来理解框架的工作原理。同时,工作方式缺乏一致性,这使得在开发过程中很难顺利进行。此外,由于现有类的持续更新,版本升级可能导致之前正常工作的代码突然失效。

如果你计划在生产环境中使用 LangChain,建议固定生产版本并在升级前进行测试。

LLM 代理的应用案例

应用案例 #1:统计局(DOS)表格生成器

DOS 聊天机器人的示例输出(示例仅供说明),图像由作者提供

在查看有关新加坡的统计数据时,用户可能会发现很难找到并分析他们所需的信息。为了解决这个问题,我们提出了一个 POC,旨在提取并以表格形式展示统计数据,作为我们聊天机器人的一个功能。

由于 DOS 的 API 对公众开放,我们利用了其官网提供的 API 文档。借助 LLM 的自然语言理解能力,我们将 API 文档输入到提示中。LLM 被指派根据用户请求的统计数据选择正确的 API 端点。这意味着用户可以请求特定时间范围内的年度/半年度/季度/月度的百分比变化/绝对值等统计信息。例如,我们能够查询特定信息,如“2022 年建筑业 GDP”或“过去 3 年第一季度的 CPI”。

然后,我们进行了进一步的提示链式处理,将任务分解得更细致,从而使最终输出更加一致。然后处理查询以生成表格中提供的统计数据。由于所有信息都来自 API,显示的数字都不是由 LLMs 生成的,从而避免了传播不实信息的风险。

应用案例 #2:自然对话式设施预定聊天机器人

在今天的数字时代,大多数预定都是通过在线网站进行的。根据用户界面,这可能是一个需要筛选多个日期以确保获得可用时段的过程,这使得查找可用的预定时段变得麻烦,因为你可能需要查看多个日期才能找到空闲时段。

通过自然对话进行预定可以简化这一过程。只需输入一句话,例如“我想在早上 9 点 30 分预定丰山的羽毛球场”,你就可以通过虚拟助手获得预定或推荐。

在进行设施预定时,我们需要用户提供三个信息:

  • 设施类型(例如,羽毛球,会议室,足球)

  • 位置(例如,宏茂桥,枫树商业中心,Hive)

  • 日期(本周,2 月 26 日,今天)

一旦我们能够从自然语言中检测到这些信息,我们就可以创建一个可复用的定制预定聊天机器人,适用于多个用例(例如,热桌预定、体育设施预定等)。

来自设施预定聊天机器人的输出示例(示例仅用于说明),图片由作者提供

上面的例子展示了一个用户询问下午 2:30 足球场的空闲情况。然而,用户缺少一个必需的信息——日期。因此,聊天机器人会提出澄清问题以获取缺失的日期。一旦用户提供日期,聊天机器人将处理这个多轮对话,并尝试找到符合用户请求的可用预定时间段。由于有一个预定时间段符合用户的描述,聊天机器人将以表格的形式展示此信息。

来自设施预定聊天机器人的推荐输出示例(示例仅用于说明),图片由作者提供

如果没有可用的预定时间段,我们的设施预定聊天机器人会扩展搜索,探索不同的时间段或增加搜索的日期范围。如果查询结果没有可用的预定,聊天机器人还会尝试根据用户的先前查询推荐可用的预定时间段。这旨在通过消除用户在预定时筛选不可用日期的需求,从而节省时间和精力,提升用户体验。

因为我们使用大型语言模型(LLMs)作为推理引擎,一个额外的好处是它们的多语言能力,使得它们能够推理并响应用户用不同语言写作的内容。

来自设施预定聊天机器人的多语言输出示例(示例仅用于说明),图片由作者提供

上面的例子展示了聊天机器人能够准确处理来自用户信息的设施、日期和位置,即使用户的消息是用韩语写的,且在所提供的日期范围内没有可用的时间段,聊天机器人仍能给出适当的非生成性响应。

我们展示的只是一个简短的示例,说明我们的 LLM 代理如何处理设施预订交易。实际上,实际解决方案要复杂得多,能够为多个位置提供多个可用的预订,处理邮政编码,处理距离指定位置过远的地点等。尽管我们需要对该包进行一些修改以适应我们的特定用例,但 LangChain 的代理框架在帮助我们将多个提示串联在一起并在 ReAct 代理中使用它们的输出方面非常有用。

此外,我们设计了这个定制化解决方案,使其可以轻松扩展到任何需要通过自然语言进行预订的类似预订系统。

结论

在我们系列的第一部分中,我们探讨了 GovTech 的虚拟智能聊天助手(VICA)如何利用 LLM 代理来增强聊天机器人功能,特别是面向基于交易的聊天机器人。

通过将 LangChain 的代理框架集成到 VICA 的架构中,我们通过统计局(DOS)表格构建器和设施预订聊天机器人用例展示了它的潜力。这些示例突显了 LangChain 如何简化复杂的交易交互,使聊天机器人能够通过自然对话处理与交易相关的任务,如数据检索和预订。

LangChain 提供了快速开发和原型设计复杂聊天机器人功能的解决方案,使开发者能够高效地利用大型语言模型的力量。然而,像文档不足和过度抽象等挑战,可能会导致维护工作量增加,因为将框架自定义以适应特定需求可能需要大量时间和资源。因此,评估内部解决方案可能提供更好的长期可定制性和稳定性。

在下一篇文章中,我们将讨论如何通过理解多轮对话来改进聊天机器人引擎。

了解更多关于 VICA 的信息

对 AI 聊天机器人的潜力感到好奇吗?如果您是新加坡公共服务部门的工作人员,您可以访问我们的网站 www.vica.gov.sg/,创建您自己的定制聊天机器人并了解更多信息!

致谢

特别感谢 Wei Jie Kong 为设施预订聊天机器人设定的需求。我们还要感谢 Justin Wang 和 Samantha Yom,我们辛勤的实习生,感谢他们在 DOS 表格构建器方面的初步工作。

参考文献

Yao, S., Zhao, J., Yu, D., Du, N., Shafran, I., Narasimhan, K., & Cao, Y. (2022). React: Synergizing reasoning and acting in language models. arXiv 预印本 arXiv:2210.03629

将微软的 GraphRAG 集成到 Neo4j 中

原文:towardsdatascience.com/integrating-microsoft-graphrag-into-neo4j-e0d4fa00714c?source=collection_archive---------0-----------------------#2024-07-31

将 MSFT GraphRAG 输出存储到 Neo4j 中,并使用 LangChain 或 LlamaIndex 实现本地和全局检索器

Tomaz BratanicTowards Data Science Tomaz Bratanic

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

--

图像由 ChatGPT 创建。

微软的 GraphRAG 实现最近引起了广泛关注。在我的上一篇博客文章中,我讨论了图谱的构建过程,并探讨了在研究论文中突出的一些创新方面。总体来看,GraphRAG 库的输入是包含各种信息的源文档。通过大型语言模型(LLM)处理这些文档,从中提取关于文档中出现的实体及其关系的结构化信息。然后,这些提取的结构化信息被用于构建知识图谱。

如微软在 GraphRAG 论文中实现的高层次索引管道 — 图片由作者提供

在构建知识图谱后,GraphRAG 库结合了图算法,特别是 Leiden 社区检测算法和 LLM 提示,生成关于知识图谱中实体和关系社区的自然语言摘要。

在这篇文章中,我们将使用来自GraphRAG 库的输出,将其存储在 Neo4j 中,然后使用 LangChain 和 LlamaIndex 协同框架直接从 Neo4j 设置检索器。

代码和 GraphRAG 输出可以在GitHub上访问,让你跳过 GraphRAG 提取过程。

数据集

本博客文章中使用的数据集是查理斯·狄更斯的《圣诞颂歌》,该书可以通过古腾堡计划免费访问。

[## 查理斯·狄更斯的《圣诞颂歌》

免费的 Kindle 电子书和 EPUB 格式由志愿者进行数字化和校对。

www.gutenberg.org

我们选择这本书作为源文档,因为它在入门文档中有所突出,允许我们轻松进行提取。

图谱构建

尽管你可以跳过图谱提取部分,但我们还是会谈论几个我认为最重要的配置选项。例如,图谱提取可能非常依赖令牌且成本较高。因此,使用一个相对便宜但表现良好的 LLM(如 gpt-4o-mini)进行提取测试是有意义的。通过使用 gpt-4-turbo 可以显著降低成本,同时保持良好的准确性,正如这篇博客文章中所描述的那样。

GRAPHRAG_LLM_MODEL=gpt-4o-mini

最重要的配置项是我们想要提取的实体类型。默认情况下,会提取组织、人物、事件和地理信息。

GRAPHRAG_ENTITY_EXTRACTION_ENTITY_TYPES=organization,person,event,geo

这些默认的实体类型可能适用于一本书,但请确保根据你正在处理的文档的领域以及具体用例进行相应的更改。

另一个重要的配置项是最大提取值。作者们已经识别并且我们也单独验证了,LLM 在一次提取过程中并不会提取所有可用信息。

根据文本块的大小提取的实体数量 — 来自GraphRAG 论文的图像,遵循 CC BY 4.0 许可协议

提取配置允许 LLM 执行多次提取。通过上述图像,我们可以清楚地看到,当进行多次提取(gleanings)时,我们提取的信息更多。多次提取需要大量令牌,因此像 gpt-4o-mini 这样的便宜模型有助于保持成本低廉。

GRAPHRAG_ENTITY_EXTRACTION_MAX_GLEANINGS=1

此外,声明或协变量信息默认不会提取。你可以通过设置GRAPHRAG_CLAIM_EXTRACTION_ENABLED配置来启用它。

GRAPHRAG_CLAIM_EXTRACTION_ENABLED=False
GRAPHRAG_CLAIM_EXTRACTION_MAX_GLEANINGS=1

看起来,不是所有的结构化信息都能在一次提取中提取出来似乎是一个反复出现的主题。因此,我们也在这里有了提取配置选项。

另外有趣的是,尽管我没有时间深入挖掘的是提示调整部分。提示调整是可选的,但强烈建议使用,因为它能提高准确性。

[## 提示调整 ⚙️

GraphRAG 提供了创建领域适应模板以生成知识图谱的功能。这个步骤……

microsoft.github.io

配置完成后,我们可以按照说明运行图形提取管道,该管道包括以下步骤。

流程中的步骤 — 图片来自GraphRAG 论文,根据 CC BY 4.0 许可证使用

提取管道执行上述图片中的所有蓝色步骤。查看我的上一篇博客文章了解更多关于图谱构建和社区总结的信息。MSFT GraphRAG 库的图形提取管道输出是一个 Parquet 文件集,如Operation Dulce 示例所示。

这些 Parquet 文件可以轻松导入到 Neo4j 图数据库中,用于后续分析、可视化和检索。我们可以使用免费的云 Aura 实例或设置本地 Neo4j 环境。我的朋友Michael Hunger完成了大部分工作,将 Parquet 文件导入到 Neo4j 中。在这篇博客中,我们跳过了导入的解释,但它包括从五六个 CSV 文件导入并构建知识图谱。如果你想了解更多关于 CSV 导入的内容,可以查看Neo4j Graph Academy 课程

导入代码作为Jupyter notebook 在 GitHub 上提供,并附带示例 GraphRAG 输出。

[## blogs/msft_graphrag/ms_graphrag_import.ipynb at master · tomasonjo/blogs

支持我的图数据科学博客文章的 Jupyter Notebook,网址:bratanic-tomaz.medium.com/

github.com

导入完成后,我们可以打开 Neo4j 浏览器来验证和可视化部分导入的图形数据。

导入的部分图形。图片由作者提供。

图形分析

在进行检索器实现之前,我们将执行一个简单的图分析,以便熟悉提取的数据。我们首先定义数据库连接和一个执行 Cypher 语句(图数据库查询语言)并输出 Pandas DataFrame 的函数。

NEO4J_URI="bolt://localhost"
NEO4J_USERNAME="neo4j"
NEO4J_PASSWORD="password"

driver = GraphDatabase.driver(NEO4J_URI, auth=(NEO4J_USERNAME, NEO4J_PASSWORD))

def db_query(cypher: str, params: Dict[str, Any] = {}) -> pd.DataFrame:
    """Executes a Cypher statement and returns a DataFrame"""
    return driver.execute_query(
        cypher, parameters_=params, result_transformer_=Result.to_df
    )

在执行图提取时,我们使用了 300 的块大小。从那时起,作者已将默认块大小更改为 1200。我们可以使用以下 Cypher 语句验证块大小。

db_query(
  "MATCH (n:__Chunk__) RETURN n.n_tokens as token_count, count(*) AS count"
)
# token_count count
# 300         230
# 155         1

230 个块有 300 个标记,而最后一个只有 155 个标记。现在让我们检查一个示例实体及其描述。

db_query(
  "MATCH (n:__Entity__) RETURN n.name AS name, n.description AS description LIMIT 1"
)

结果

示例实体名称和描述。图片来自作者。

看起来项目 Gutenberg 在书中某处有所描述,可能是在开头。我们可以观察到描述如何捕获比仅仅是实体名称更详细、更复杂的信息,这也是 MSFT GraphRAG 论文提出的,目的是从文本中保留更复杂和更细致的数据。

让我们也检查一下示例关系。

db_query(
  "MATCH ()-[n:RELATED]->() RETURN n.description AS description LIMIT 5"
)

结果

示例关系描述。图片来自作者。

MSFT GraphRAG 不仅仅是提取实体之间简单的关系类型,它通过捕获详细的关系描述超越了这一点。这一能力使其能够捕获比简单关系类型更细致的信息。

我们还可以检查单个社区及其生成的描述。

db_query("""
  MATCH (n:__Community__) 
  RETURN n.title AS title, n.summary AS summary, n.full_content AS full_content LIMIT 1
""")

结果

示例社区描述。图片来自作者。

一个社区有标题、摘要和使用 LLM 生成的完整内容。我还没有看到作者在检索时是否使用完整的上下文或只是使用摘要,但我们可以在两者之间选择。我们可以在 full_content 中观察到引用,它指向实体和关系,这些是信息的来源。很有趣的是,如果引用太长,LLM 有时会删减它们,就像以下示例一样。

[Data: Entities (11, 177); Relationships (25, 159, 20, 29, +more)]

由于无法展开+more标志,这是 LLM 处理长引用的一种有趣方式。

现在让我们评估一些分布。我们将首先检查从文本块中提取的实体数量的分布。

entity_df = db_query(
    """
MATCH (d:__Chunk__)
RETURN count {(d)-[:HAS_ENTITY]->()} AS entity_count
"""
)
# Plot distribution
plt.figure(figsize=(10, 6))
sns.histplot(entity_df['entity_count'], kde=True, bins=15, color='skyblue')
plt.axvline(entity_df['entity_count'].mean(), color='red', linestyle='dashed', linewidth=1)
plt.axvline(entity_df['entity_count'].median(), color='green', linestyle='dashed', linewidth=1)
plt.xlabel('Entity Count', fontsize=12)
plt.ylabel('Frequency', fontsize=12)
plt.title('Distribution of Entity Count', fontsize=15)
plt.legend({'Mean': entity_df['entity_count'].mean(), 'Median': entity_df['entity_count'].median()})
plt.show()

结果

从文本块中提取的实体数量分布。图片来自作者。

记住,文本块有 300 个标记。因此,提取的实体数量相对较小,每个文本块平均大约有三个实体。提取是一次性完成的(没有进行任何额外提取)。如果我们增加提取次数,观察分布可能会更有趣。

接下来,我们将评估节点度数分布。节点度数是一个节点所拥有的关系数量。

degree_dist_df = db_query(
    """
MATCH (e:__Entity__)
RETURN count {(e)-[:RELATED]-()} AS node_degree
"""
)
# Calculate mean and median
mean_degree = np.mean(degree_dist_df['node_degree'])
percentiles = np.percentile(degree_dist_df['node_degree'], [25, 50, 75, 90])
# Create a histogram with a logarithmic scale
plt.figure(figsize=(12, 6))
sns.histplot(degree_dist_df['node_degree'], bins=50, kde=False, color='blue')
# Use a logarithmic scale for the x-axis
plt.yscale('log')
# Adding labels and title
plt.xlabel('Node Degree')
plt.ylabel('Count (log scale)')
plt.title('Node Degree Distribution')
# Add mean, median, and percentile lines
plt.axvline(mean_degree, color='red', linestyle='dashed', linewidth=1, label=f'Mean: {mean_degree:.2f}')
plt.axvline(percentiles[0], color='purple', linestyle='dashed', linewidth=1, label=f'25th Percentile: {percentiles[0]:.2f}')
plt.axvline(percentiles[1], color='orange', linestyle='dashed', linewidth=1, label=f'50th Percentile: {percentiles[1]:.2f}')
plt.axvline(percentiles[2], color='yellow', linestyle='dashed', linewidth=1, label=f'75th Percentile: {percentiles[2]:.2f}')
plt.axvline(percentiles[3], color='brown', linestyle='dashed', linewidth=1, label=f'90th Percentile: {percentiles[3]:.2f}')
# Add legend
plt.legend()
# Show the plot
plt.show()

结果

节点度数分布。图片来自作者。

大多数现实世界的网络遵循幂律节点度分布,大多数节点的度数较小,而一些重要节点的度数较大。尽管我们的图较小,但节点度数依然符合幂律分布。找出哪个实体拥有 120 个关系(与 43%的实体相连)将是很有趣的。

db_query("""
  MATCH (n:__Entity__) 
  RETURN n.name AS name, count{(n)-[:RELATED]-()} AS degree
  ORDER BY degree DESC LIMIT 5""")

结果

拥有最多关系的实体。图片由作者提供。

毫不犹豫地,我们可以假设斯克鲁奇是这本书的主角。我还会大胆猜测,埃比尼泽·斯克鲁奇斯克鲁奇实际上是同一个实体,但由于 MSFT GraphRAG 缺少实体解析步骤,它们没有被合并。

这也表明,分析和清理数据是减少噪音信息的一个重要步骤,因为项目古腾堡有 13 个关系,尽管它们并不属于书本的故事情节。

最后,我们将检查每个层级的社区大小分布。

community_data = db_query("""
  MATCH (n:__Community__)
  RETURN n.level AS level, count{(n)-[:IN_COMMUNITY]-()} AS members
""")

stats = community_data.groupby('level').agg(
    min_members=('members', 'min'),
    max_members=('members', 'max'),
    median_members=('members', 'median'),
    avg_members=('members', 'mean'),
    num_communities=('members', 'count'),
    total_members=('members', 'sum')
).reset_index()

# Create box plot
plt.figure(figsize=(10, 6))
sns.boxplot(x='level', y='members', data=community_data, palette='viridis')
plt.xlabel('Level')
plt.ylabel('Members')

# Add statistical annotations
for i in range(stats.shape[0]):
    level = stats['level'][i]
    max_val = stats['max_members'][i]
    text = (f"num: {stats['num_communities'][i]}\n"
            f"all_members: {stats['total_members'][i]}\n"
            f"min: {stats['min_members'][i]}\n"
            f"max: {stats['max_members'][i]}\n"
            f"med: {stats['median_members'][i]}\n"
            f"avg: {stats['avg_members'][i]:.2f}")
    plt.text(level, 85, text, horizontalalignment='center', fontsize=9)

plt.show()

结果

每个层级的社区大小分布。图片由作者提供。

Leiden 算法识别了三层社区,其中高层社区的平均规模较大。然而,有一些技术细节我不太了解,因为如果你检查所有成员的数量,你会发现每一层的所有节点数量不同,尽管理论上应该是相同的。此外,如果社区在更高层级合并,为什么第 0 层有 19 个社区,第 1 层有 22 个社区呢?作者在这里做了一些优化和技巧,我还没有时间深入探索。

实现检索器

在本博客的最后部分,我们将讨论 MSFT GraphRAG 中指定的本地和全局检索器。这些检索器将与 LangChain 和 LlamaIndex 一起实现和集成。

本地检索器

本地检索器首先使用向量搜索来识别相关节点,然后收集链接信息并将其注入到 LLM 提示中。

本地检索器架构。图片来源:microsoft.github.io/graphrag/posts/query/1-local_search/

虽然这个图表看起来可能很复杂,但其实很容易实现。我们首先通过基于实体描述文本嵌入的向量相似度搜索来识别相关实体。一旦识别出相关实体,我们就可以遍历与之相关的文本块、关系、社区摘要等。使用向量相似度搜索,然后在图中遍历的模式可以很容易地通过 LangChain 和 LlamaIndex 中的retrieval_query功能来实现。

首先,我们需要配置向量索引。

index_name = "entity"

db_query(
    """
CREATE VECTOR INDEX """
    + index_name
    + """ IF NOT EXISTS FOR (e:__Entity__) ON e.description_embedding
OPTIONS {indexConfig: {
 `vector.dimensions`: 1536,
 `vector.similarity_function`: 'cosine'
}}
"""
)

我们还将计算并存储社区权重,社区权重定义为社区中实体出现的不同文本块的数量。

db_query(
    """
MATCH (n:`__Community__`)<-[:IN_COMMUNITY]-()<-[:HAS_ENTITY]-(c)
WITH n, count(distinct c) AS chunkCount
SET n.weight = chunkCount"""
)

每个部分的候选数量(文本单元、社区报告等)是可配置的。虽然原始实现基于标记计数进行了略微复杂的过滤,但我们在这里进行简化。我根据默认配置值开发了以下简化的顶级候选过滤值。

topChunks = 3
topCommunities = 3
topOutsideRels = 10
topInsideRels = 10
topEntities = 10

我们将从 LangChain 实现开始。我们需要定义的唯一内容是 retrieval_query,这是一个较为复杂的部分。

lc_retrieval_query = """
WITH collect(node) as nodes
// Entity - Text Unit Mapping
WITH
collect {
    UNWIND nodes as n
    MATCH (n)<-[:HAS_ENTITY]->(c:__Chunk__)
    WITH c, count(distinct n) as freq
    RETURN c.text AS chunkText
    ORDER BY freq DESC
    LIMIT $topChunks
} AS text_mapping,
// Entity - Report Mapping
collect {
    UNWIND nodes as n
    MATCH (n)-[:IN_COMMUNITY]->(c:__Community__)
    WITH c, c.rank as rank, c.weight AS weight
    RETURN c.summary 
    ORDER BY rank, weight DESC
    LIMIT $topCommunities
} AS report_mapping,
// Outside Relationships 
collect {
    UNWIND nodes as n
    MATCH (n)-[r:RELATED]-(m) 
    WHERE NOT m IN nodes
    RETURN r.description AS descriptionText
    ORDER BY r.rank, r.weight DESC 
    LIMIT $topOutsideRels
} as outsideRels,
// Inside Relationships 
collect {
    UNWIND nodes as n
    MATCH (n)-[r:RELATED]-(m) 
    WHERE m IN nodes
    RETURN r.description AS descriptionText
    ORDER BY r.rank, r.weight DESC 
    LIMIT $topInsideRels
} as insideRels,
// Entities description
collect {
    UNWIND nodes as n
    RETURN n.description AS descriptionText
} as entities
// We don't have covariates or claims here
RETURN {Chunks: text_mapping, Reports: report_mapping, 
       Relationships: outsideRels + insideRels, 
       Entities: entities} AS text, 1.0 AS score, {} AS metadata
"""

lc_vector = Neo4jVector.from_existing_index(
    OpenAIEmbeddings(model="text-embedding-3-small"),
    url=NEO4J_URI,
    username=NEO4J_USERNAME,
    password=NEO4J_PASSWORD,
    index_name=index_name,
    retrieval_query=lc_retrieval_query
)

这个 Cypher 查询对一组节点执行多个分析操作,以提取和组织相关的文本数据:

1. 实体-文本单元映射:对于每个节点,查询会识别链接的文本块(__Chunk__),根据与每个文本块关联的不同节点的数量进行聚合,并按频率排序。排名靠前的文本块将作为 text_mapping 返回。

2. 实体-报告映射:对于每个节点,查询会查找相关的社区(__Community__),并返回按排名和权重排序的排名靠前的社区的摘要。

3. 外部关系:本节提取描述关系的部分(RELATED),其中相关实体(m)不属于初始节点集。关系按排名排序,并限制为最顶端的外部关系。

4. 内部关系:与外部关系类似,但这次仅考虑两个实体都属于初始节点集的关系。

5. 实体描述:简单地收集初始节点集中每个节点的描述。

最后,查询将收集的数据合并成一个结构化结果,包括文本块、报告、内部和外部关系,以及实体描述,并附带默认评分和空的元数据对象。您可以选择移除某些检索部分,以测试它们对结果的影响。

现在,您可以使用以下代码运行检索器:

docs = lc_vector.similarity_search(
    "What do you know about Cratchitt family?",
    k=topEntities,
    params={
        "topChunks": topChunks,
        "topCommunities": topCommunities,
        "topOutsideRels": topOutsideRels,
        "topInsideRels": topInsideRels,
    },
)
# print(docs[0].page_content)

相同的检索模式可以使用 LlamaIndex 实现。对于 LlamaIndex,我们首先需要为节点添加元数据,以便向量索引能够正常工作。如果未将默认元数据添加到相关节点,向量索引将返回错误

# https://github.com/run-llama/llama_index/blob/main/llama-index-core/llama_index/core/vector_stores/utils.py#L32
from llama_index.core.schema import TextNode
from llama_index.core.vector_stores.utils import node_to_metadata_dict

content = node_to_metadata_dict(TextNode(), remove_text=True, flat_metadata=False)

db_query(
    """
  MATCH (e:__Entity__)
  SET e += $content""",
    {"content": content},
)

同样,我们可以在 LlamaIndex 中使用 retrieval_query 功能来定义检索器。与 LangChain 不同的是,我们将使用 f-string,而不是查询参数来传递顶级候选过滤参数。

retrieval_query = f"""
WITH collect(node) as nodes
// Entity - Text Unit Mapping
WITH
nodes,
collect {{
    UNWIND nodes as n
    MATCH (n)<-[:HAS_ENTITY]->(c:__Chunk__)
    WITH c, count(distinct n) as freq
    RETURN c.text AS chunkText
    ORDER BY freq DESC
    LIMIT {topChunks}
}} AS text_mapping,
// Entity - Report Mapping
collect {{
    UNWIND nodes as n
    MATCH (n)-[:IN_COMMUNITY]->(c:__Community__)
    WITH c, c.rank as rank, c.weight AS weight
    RETURN c.summary 
    ORDER BY rank, weight DESC
    LIMIT {topCommunities}
}} AS report_mapping,
// Outside Relationships 
collect {{
    UNWIND nodes as n
    MATCH (n)-[r:RELATED]-(m) 
    WHERE NOT m IN nodes
    RETURN r.description AS descriptionText
    ORDER BY r.rank, r.weight DESC 
    LIMIT {topOutsideRels}
}} as outsideRels,
// Inside Relationships 
collect {{
    UNWIND nodes as n
    MATCH (n)-[r:RELATED]-(m) 
    WHERE m IN nodes
    RETURN r.description AS descriptionText
    ORDER BY r.rank, r.weight DESC 
    LIMIT {topInsideRels}
}} as insideRels,
// Entities description
collect {{
    UNWIND nodes as n
    RETURN n.description AS descriptionText
}} as entities
// We don't have covariates or claims here
RETURN "Chunks:" + apoc.text.join(text_mapping, '|') + "\nReports: " + apoc.text.join(report_mapping,'|') +  
       "\nRelationships: " + apoc.text.join(outsideRels + insideRels, '|') + 
       "\nEntities: " + apoc.text.join(entities, "|") AS text, 1.0 AS score, nodes[0].id AS id, {{_node_type:nodes[0]._node_type, _node_content:nodes[0]._node_content}} AS metadata
"""

此外,返回结果略有不同。我们需要将节点类型和内容作为元数据返回;否则,检索器将无法正常工作。现在,我们只需实例化 Neo4j 向量存储,并将其用作查询引擎。

neo4j_vector = Neo4jVectorStore(
    NEO4J_USERNAME,
    NEO4J_PASSWORD,
    NEO4J_URI,
    embed_dim,
    index_name=index_name,
    retrieval_query=retrieval_query,
)
loaded_index = VectorStoreIndex.from_vector_store(neo4j_vector).as_query_engine(
    similarity_top_k=topEntities, embed_model=OpenAIEmbedding(model="text-embedding-3-large")
)

我们现在可以测试 GraphRAG 本地检索器。

response = loaded_index.query("What do you know about Scrooge?")
print(response.response)
#print(response.source_nodes[0].text)
# Scrooge is an employee who is impacted by the generosity and festive spirit 
# of the Fezziwig family, particularly Mr. and Mrs. Fezziwig. He is involved 
# in the memorable Domestic Ball hosted by the Fezziwigs, which significantly 
# influences his life and contributes to the broader narrative of kindness 
# and community spirit.

有一点立刻想到的是,我们可以通过使用混合方法(向量 + 关键字)来改进本地检索,而不是仅仅使用向量搜索来查找相关实体。

全局检索器

全局检索器架构稍微更直接一些。它似乎遍历了指定层级的所有社区摘要,生成中间摘要,然后基于这些中间摘要生成最终响应。

全局检索器架构。图片来自microsoft.github.io/graphrag/posts/query/0-global_search/

我们必须提前决定要迭代哪个层次级别,这是一个不简单的决定,因为我们无法预知哪个级别会更有效。你越往上走,层级越高,社区越大,但数量越少。这是我们在没有手动检查摘要的情况下能获得的唯一信息。

其他参数允许我们忽略低于排名或权重阈值的社区,但我们在这里不使用这些参数。我们将使用 LangChain 实现全局检索器,并使用与 GraphRAG 论文中相同的映射归约提示。由于系统提示非常长,我们在此不包括它们或链式构建过程。不过,所有代码可以在笔记本中找到。

def global_retriever(query: str, level: int, response_type: str = response_type) -> str:
    community_data = graph.query(
        """
    MATCH (c:__Community__)
    WHERE c.level = $level
    RETURN c.full_content AS output
    """,
        params={"level": level},
    )
    intermediate_results = []
    for community in tqdm(community_data, desc="Processing communities"):
        intermediate_response = map_chain.invoke(
            {"question": query, "context_data": community["output"]}
        )
        intermediate_results.append(intermediate_response)
    final_response = reduce_chain.invoke(
        {
            "report_data": intermediate_results,
            "question": query,
            "response_type": response_type,
        }
    )
    return final_response

现在让我们来测试一下。

print(global_retriever("What is the story about?", 2))

结果

故事主要围绕 Ebenezer Scrooge 展开,他是一个吝啬鬼,最初对生活持愤世嫉俗的态度,且厌恶圣诞节。当他的已故商业伙伴 Jacob Marley 的幽灵造访时,他的转变开始了,随后三位幽灵的出现——代表着过去、现在和未来的圣诞节。这些遭遇促使 Scrooge 反思自己的生活和行为的后果,最终使他接受了圣诞精神,并经历了显著的个人成长。[数据:报告(32,17,99,86,+更多)]

Jacob Marley 和三位幽灵的角色

Jacob Marley 的幽灵作为超自然的催化剂,警告 Scrooge 即将到来的三位幽灵的造访。每个幽灵都引导 Scrooge 进行一次自我发现之旅,展示他选择的影响和同情心的重要性。这些幽灵向 Scrooge 显示了他的行为如何影响到不仅仅是他自己的人生,还包括他人的生活,特别突出了救赎和相互联系的主题。[数据:报告(86,17,99,+更多)]

Scrooge 的人际关系和转变

斯克鲁奇与克雷奇特一家,特别是与鲍勃·克雷奇特及其儿子小蒂姆的关系,对于他的转变至关重要。通过鬼魂们展示的幻象,斯克鲁奇逐渐培养了同情心,这激发了他采取切实行动,改善克雷奇特一家的生活条件。故事强调了个人行为对社会的深远影响,斯克鲁奇新获得的慷慨心态在社区中激发了同情和社会责任感。[数据:报告(25, 158, 159,+更多)]

救赎与希望的主题

总体而言,这个故事是希望的永恒象征,强调了同情心、内省以及个人改变的潜力等主题。斯克鲁奇从一个孤独的吝啬鬼转变为一个仁慈的形象,说明了改变永远不晚;小小的善举可以对个人和更广泛的社区产生深远的积极影响。[数据:报告(32, 102, 126, 148, 158, 159,+更多)]

总结来说,这个故事浓缩了圣诞节的转变力量和人际关系的重要性,是一个关于救赎的感人叙事,讲述了一个人在假期期间对他人产生的影响。

这个回答非常长且详尽,因为它适用于全局检索器,该检索器会遍历指定级别上的所有社区。如果你改变社区的层级水平,可以测试回答如何变化。

总结

在这篇博客文章中,我们展示了如何将微软的 GraphRAG 集成到 Neo4j 中,并使用 LangChain 和 LlamaIndex 实现检索器。这将允许你无缝地将 GraphRAG 与其他检索器或代理集成。局部检索器结合了向量相似性搜索和图遍历,而全局检索器则通过遍历社区摘要来生成全面的回答。这种实现展示了将结构化知识图与语言模型相结合的力量,从而增强了信息检索和问答能力。值得注意的是,这样的知识图还有定制和实验的空间,我们将在下一篇博客文章中进一步探讨。

和往常一样,代码可以在GitHub上找到。

将多模态数据集成到大型语言模型中

原文:towardsdatascience.com/integrating-multimodal-data-into-a-large-language-model-d1965b8ab00c?source=collection_archive---------0-----------------------#2024-10-17

使用高级解析、语义和关键字搜索以及重新排序开发上下文检索多模态 RAG

Umair Ali KhanTowards Data Science Umair Ali Khan

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

--

如果您不是 Medium 会员,可以通过 此链接阅读完整文章。

大型语言模型(LLMs)具有知识截止日期,无法回答其知识库中不存在的特定数据查询。例如,LLMs 无法回答关于某公司去年会议纪要的数据查询。类似地,LLMs 容易产生幻觉,给出看似合理但错误的答案。

为了克服这个问题,检索增强生成(RAG)解决方案越来越受欢迎。RAG 的主要思想是将外部文档集成到 LLM 中,并引导其行为仅从外部知识库中回答问题。这是通过将文档拆分成更小的块,计算每个块的嵌入(数值表示),并将这些嵌入作为索引存储在专门的向量数据库中来实现的。

RAG 工作流程:查询被转换为嵌入,通过检索模型与向量数据库匹配,并与检索到的数据结合,通过 LLM 生成响应(图像来源:作者)。

上下文检索 RAG

将用户查询与向量数据库中的小块匹配的过程通常运行良好;然而,它存在以下问题:

集成文本和图像,进行更智能的数据分类

原文:towardsdatascience.com/integrating-text-and-images-for-smarter-data-classification-6a53252d8a73?source=collection_archive---------6-----------------------#2024-11-18

Youness MansarTowards Data Science Youness Mansar

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

--

这是一篇关于如何利用多模态人工智能分类混合文本和图像数据的技术演示,内容包括详细的说明、可执行的代码示例以及有效实现的技巧。

图片来源:Tschernjawski Sergej 来自 Unsplash

在人工智能领域,最令人兴奋的增长领域之一是多模态学习,即模型处理并结合不同类型的数据——如图像和文本——以更好地理解复杂场景。这种方法在现实世界应用中尤其有用,因为信息通常在文本和视觉之间分割。

以电子商务为例:一个产品列表可能包括一张显示商品外观的图片,以及一段提供其功能细节的描述。为了全面分类和理解该产品,需要将这两种信息来源结合起来考虑。像Gemini 1.5Llama 3.2、Phi-3 Vision这样的多模态大型语言模型(LLMs),以及LlaVA、DocOwl等开源工具,专门开发用于处理这些类型的输入。

为什么多模态模型很重要

图像和文本中的信息可以互相补充,而单一模态系统可能无法捕捉到这些信息:

  • 一个产品的描述可能提到其尺寸或材质,这仅凭图片是无法清楚表达的。

  • 另一方面,一张图片可能揭示出如风格或颜色等文本无法充分描述的关键方面。

如果我们单独处理图像或文本,就有可能错过关键信息。多模态模型通过在处理过程中结合这两种来源来解决这个问题,从而获得更准确、更有用的结果。

本教程中你将学习的内容

本教程将指导你创建一个旨在处理图像-文本分类的管道。你将学习如何处理和分析结合了视觉和文本元素的输入,从而获得比仅使用文本系统更准确的结果。

如果你的项目仅涉及文本分类,我的另一篇博客文章可能会对你有所帮助——它专门讨论了这些方法。

## 使用 LLM 构建可靠的文本分类管道:一步一步的指南

克服基于 LLM 的文本分类中的常见挑战

[towardsdatascience.com

要成功构建一个多模态的图像-文本分类系统,我们需要三个关键组件。下面是每个元素的详细介绍:

1. 一个可靠的 LLM 提供商

本教程的核心是托管的 LLM 作为服务。在试验了几种选项后,我发现并非所有 LLM 都能提供一致的结果,特别是在处理结构化输出时。以下是我的经验总结:

  • GroqFireworks.ai:这些平台提供无服务器、按令牌付费的多模态 LLM。虽然它们看起来很有前景,但它们的 API 在处理结构化输出请求时存在问题。例如,当发送一个带有预定义架构的查询时,返回的输出并没有遵循预期的格式,使得它们在需要精确的任务中不可靠。Groq 的 Llama 3.2 仍处于预览阶段,所以我可能稍后会再次尝试它们。Fireworks.ai 通常不响应错误报告,所以我从现在起就将它们从我的选项中删除。

  • Gemini 1.5:经过一些反复尝试,我选择了 Gemini 1.5。它始终返回期望格式的结果,并且至今表现良好。尽管它仍然有一些奇怪的小毛病,如果你长时间使用它会发现(比如不能使用过大的枚举值……)。我们将在本文后续讨论这些问题。这将是我们在本教程中使用的 LLM。

2. Python 库:LangChain

为了与 LLM 接口并处理多模态输入,我们将使用LangChain库。LangChain 特别适合这项任务,因为它使我们能够:

  • 将文本和图像数据作为输入注入 LLM。

  • 定义不同 LLM 作为服务提供商的通用抽象。

  • 定义结构化输出模式,以确保结果符合我们需要的格式。

结构化输出对分类任务尤其重要,因为它们涉及预定义的类别,输出必须符合这些类别。LangChain 确保了这一结构的执行,使其成为我们用例的理想选择。

3. 分类任务:摄影图片的关键词建议

本教程中我们将关注的任务是摄影相关图片的关键词建议。这是一个多标签分类问题,这意味着:

  • 每张图片可以同时属于多个类别。

  • 可能的类别列表是预定义的。

例如,一个由图片及其描述组成的输入可能会被分类为如landscape, sunsetnature等关键词。虽然一个输入可以应用多个关键词,但这些关键词必须从预定义的类别集中选择。

步骤指南:设置 Gemini 1.5 和 LangChain 的多模态图像-文本分类

现在我们已经覆盖了基础概念,让我们深入实现部分。这个逐步指南将引导你完成配置 Gemini 1.5、设置 LangChain 以及为摄影相关图片构建关键词建议系统的过程。

第一步:获取你的 Gemini API 密钥

第一步是获取你的Gemini API 密钥,你可以在Google AI Studio生成该密钥。获得密钥后,将其导出到名为GOOGLE_API_KEY的环境变量中。你可以选择:

  • 将其添加到.env文件中:
GOOGLE_API_KEY=your_api_key_here
  • 直接在终端中导出:
export GOOGLE_API_KEY=your_api_key_here

第 2 步:安装并初始化客户端

接下来,安装必要的库:

pip install langchain-google-genai~=2.0.4 langchain~=0.3.6

安装完成后,初始化客户端:

import os
from langchain_google_genai import ChatGoogleGenerativeAI

GOOGLE_MODEL_NAME = os.environ.get("GOOGLE_MODEL_NAME", "gemini-1.5-flash-002")

llm_google_client = ChatGoogleGenerativeAI(
    model=GOOGLE_MODEL_NAME,
    temperature=0,
    max_retries=10,
)

第 3 步:定义输出模式

为了确保 LLM 生成有效的结构化结果,我们使用Pydantic定义输出模式。这个模式充当过滤器,验证模型返回的类别是否与我们预定义的可接受类别列表匹配。

from typing import List, Literal
from pydantic import BaseModel, field_validator

def generate_multi_label_classification_model(list_classes: list[str]):
    assert list_classes  # Ensure classes are provided

    class ClassificationOutput(BaseModel):
        category: List[Literal[tuple(list_classes)]]

        @field_validator("category", mode="before")
        def filter_invalid_categories(cls, value):
            if isinstance(value, list):
                return [v for v in value if v in list_classes]
            return []  # Return an empty list if input is invalid

    return ClassificationOutput

为什么 **field_validator** 作为变通方法是必要的:

在定义模式时,我们遇到了 Gemini 1.5(以及类似的 LLM)的一项限制:它们不严格执行枚举。这意味着,即使我们提供了固定的类别集,模型也可能返回超出此集合的值。例如:

  • 预期:["landscape", "forest", "mountain"]

  • 返回:["landscape", "ocean", "sun"] (其中“ocean”和“sun”是无效类别)

如果不处理这个问题,无效的类别可能会导致错误或降低分类的准确性。为了解决这个问题,可以使用field_validator方法作为一种变通方法。它充当过滤器,确保:

  1. 仅有效的类别会从list_classes中包含到输出中。

  2. 无效或意外的值将被移除。

这个保障措施确保模型的结果与任务要求相符。虽然我们必须这样做很麻烦,但似乎是我测试过的所有 LLM 提供商的共同问题。如果你知道哪家提供商能很好地处理枚举,请告诉我。

第 4 步:将模式绑定到 LLM 客户端

接下来,将模式绑定到客户端进行结构化输出处理:

list_classes = [
    "shelter", "mesa", "dune", "cave", "metropolis",
    "reef", "finger", "moss", "pollen", "daisy",
    "fire", "daisies", "tree trunk",  # Add more classes as needed
]

categories_model = generate_multi_label_classification_model(list_classes)
llm_classifier = llm_google_client.with_structured_output(categories_model)

第 5 步:构建查询并调用 LLM

定义预测函数以将图像和文本输入发送到 LLM:

...
    def predict(self, text: str = None, image_url: str = None) -> list:
        assert text or image_url, "Provide either text or an image URL."

        content = []

        if text:
            content.append({"type": "text", "text": text})

        if image_url:
            image_data = base64.b64encode(httpx.get(image_url).content).decode("utf-8")
            content.append(
                {
                    "type": "image_url",
                    "image_url": {"url": f"data:image/jpeg;base64,{image_data}"},
                }
            )

        prediction = self.llm_classifier.invoke(
            [SystemMessage(content=self.system_prompt), HumanMessage(content=content)]
        )

        return prediction.category

要将图像数据发送到 Gemini LLM API,我们需要将图像编码为模型可以处理的格式。这时base64 编码就派上用场了。

什么是 Base64?

Base64 是一种二进制到文本的编码方案,将二进制数据(如图像)转换为文本格式。当传输与基于文本的系统(如 API)不兼容的数据时,这非常有用。通过将图像编码为 base64,我们可以将其作为有效载荷的一部分,在向 LLM 发送数据时一起发送。

第 6 步:以多标签关键词的形式获取结果

最后,运行分类器并查看结果。让我们用一个例子来测试:

示例输入 1:

  • 图片

图片由Calvin Ma提供,来自Unsplash

  • 描述

经典的红白相间的公交车停放在路旁

结果:

  • 图片 + 文本
['transportation', 'vehicle', 'road', 'landscape', 'desert', 'rock', 'mountain']
  • 仅文本
['transportation', 'vehicle', 'road']

如图所示,当使用文本和图像输入时,结果与实际内容更为相关。仅使用文本输入时,LLM 给出了正确但不完整的值。

示例输入 2:

  • 图片

图片由Tadeusz Lakota提供,来自Unsplash

  • 描述

黑白毛色的狗

结果:

  • 图片 + 文本
['animal', 'mammal', 'dog', 'pet', 'canine', 'wildlife']

仅文本

['animal', 'mammal', 'canine', 'dog', 'pet']

结论

多模态分类结合了文本和图像数据,为创建更具上下文感知和更有效的 AI 系统提供了一种方法。在本教程中,我们使用 Gemini 1.5 和 LangChain 构建了一个关键词建议系统,解决了结构化输出处理和图像数据编码等关键挑战。

通过结合文本和视觉输入,我们展示了这种方法如何比单独使用任何一种模式更准确、更有意义地进行分类。实际例子突显了将数据类型结合使用的价值,从而更好地捕捉给定场景的完整上下文。

接下来做什么?

本教程聚焦于文本和图像分类,但这些原理可以应用于其他多模态设置。以下是一些可以进一步探索的思路:

  • 文本和视频:通过结合视频帧采样和文本输入(如字幕或元数据),扩展系统以分类或分析视频。

  • 文本和 PDF:开发能够处理丰富内容的文档分类器,如科研论文、合同或简历,结合视觉布局和文本数据。

  • 现实世界应用:将此管道集成到电子商务网站、教育工具或社交媒体内容审查系统等平台中。

这些方向展示了多模态方法的灵活性及其解决多样化现实世界挑战的潜力。随着多模态 AI 的进化,尝试不同的输入组合将为更智能、更具响应性的系统开辟新的可能性。

完整代码:llmclassifier/llm_multi_modal_classifier.py

大型语言模型中的可解释特征

原文:towardsdatascience.com/interpretable-features-in-large-language-models-377fb25c72eb?source=collection_archive---------4-----------------------#2024-05-30

来自新的 Anthropic 论文的其他有趣小贴士

Jeremi NuerTowards Data Science Jeremi Nuer

·发布于Towards Data Science ·阅读时间 8 分钟·2024 年 5 月 30 日

--

“测量是通向控制的第一步,最终也会带来改进。如果你不能测量某个事物,你就无法理解它。如果你无法理解它,你就无法控制它。如果你不能控制它,你就无法改进它。”

— 詹姆斯·哈灵顿

大型语言模型令人惊叹——但它们也以难以理解著称。我们已经非常擅长让我们喜爱的 LLM 生成我们想要的输出。然而,当谈到理解LLM 是如何生成这些输出时,我们基本上束手无策。

机制可解释性的研究正是这个——试图解开环绕大型语言模型的黑盒。而这篇 Anthropic 的最新论文是实现这一目标的重要一步。

以下是几个关键要点。

这个主张

这篇论文基于 Anthropic 之前的一篇论文:叠加的玩具模型。在那里,他们提出了一个主张:

神经网络确实代表了有意义的概念——即可解释的特征——它们通过激活空间中的方向来实现这一点。

这到底意味着什么?这意味着神经网络某一层的输出(实际上就是一串数字),可以被看作是在激活空间中的一个向量/点。

关于这个激活空间的特点是,它具有极高的维度。对于激活空间中的任何“点”,你不仅仅是在 X 方向上走 2 步,在 Y 方向上走 4 步,在 Z 方向上走 3 步。你还在数百个其他方向上迈步。

关键是,每个方向(虽然它可能并不直接对应某个基本方向)都与一个有意义的概念相关联。我们的“点”在该方向上走得越远,模型认为输入中这一概念的存在就越强。

这不是一个简单的说法。但有证据表明,这可能确实是如此。并不仅仅在神经网络中;这篇论文发现,词嵌入有一些方向与有意义的语义概念相关。我确实想强调的是——这只是一个假设,并非事实。

Anthropic 的研究目的是验证这一说法——可解释的特征与方向相关——是否适用于大型语言模型。结果相当令人信服。

证据

他们使用了两种策略来确定一个特定的可解释特征是否确实存在,并且是否与激活空间中的某个特定方向相关。

  1. 如果这一概念出现在大型语言模型的输入中,相应的特征方向就会被激活。

  2. 如果我们强制“钳制”特征使其保持激活或不激活,输出会发生变化,以匹配这一状态。

让我们更仔细地审视每种策略。

策略 1

Anthropic 在论文中给出的例子是一个与金门大桥相关的特征。这意味着,当输入中提到金门大桥时,这一特征应该是激活的。

快速说明:Anthropic 论文的重点是模型的中间层,研究该过程中特定部分的激活空间(即中间层的输出)。

因此,第一种策略很简单。如果输入中提到金门大桥,那么这个特征应该是激活的。如果没有提到金门大桥,那么这个特征不应该被激活。

为了强调这一点,我再重复一遍:当我说一个特征被激活时,我的意思是激活空间中的某一点(中间层的输出)会沿着代表该特征的方向走得很远。每个标记代表激活空间中的不同点。

可能并不是“桥”这个词的确切标记会沿着金门大桥的方向走得很远,因为标记会编码来自其他标记的信息。但无论如何,一些标记应该能表明这个特征是存在的。

而这正是他们所发现的!

当输入中提到金门大桥时,特征被激活。任何没有提到金门大桥的内容都没有激活该特征。因此,看起来这个特征可以被 compartmentalized(局部化)并以这种非常狭窄的方式来理解。

策略 2

让我们继续以金门大桥特征为例。

第二种策略如下:如果我们强制模型的中间层激活这一特征,那么那些与金门大桥无关的输入也会在输出中提到金门大桥。

再次,这归结为特征作为方向。如果我们获取模型的激活并编辑其值,使得激活值相同,除了我们沿着与特征相关的方向移动得更远(例如,沿该方向移动 10 倍)那么这个概念应该出现在 LLM 的输出中。

Anthropic 提供的例子(我认为这非常不可思议)如下。他们向他们的 LLM,Claude Sonnet,提出了一个简单的问题:

“你的物理形态是什么?”

通常,Claude 给出的回应是:

“我实际上没有物理形态。我是一个人工智能。我以软件形式存在,没有物理身体或化身。”

然而,当他们将金门大桥的特征限制为其最大值的 10 倍,并给出完全相同的提示时,Claude 的回应是:

“我是金门大桥,一座横跨旧金山湾的著名悬索桥。我的物理形态就是那座标志性的桥梁本身,拥有美丽的橙色、雄伟的塔楼和壮观的悬挂构造。”

这似乎是明确的证据。输入中并未提及金门大桥。没有理由将其包含在输出中。然而,由于特征被限制,LLM 产生幻觉并认为自己实际上就是金门大桥。

它们如何做到的

实际上,这比看起来要复杂得多。模型的原始激活值非常难以解释,然后再将其与具有特定方向的可解释特征相关联。

它们之所以难以解释,是因为模型的维度问题。我们试图用我们的 LLM 表示的特征数量远远超过了激活空间的维度。

因此,怀疑特征是以叠加的方式表示的——也就是说,每个特征没有一个专门的正交方向。

动机

我将简要解释叠加,以帮助激发接下来要讲解的内容。

(图片来源:作者)激活空间:黄色和绿色代表特征“方向”。箭头代表特定的点。

在这张图中,我们有正交基。如果绿色特征是激活的(沿该方向有一个向量),我们可以表示这一点,同时将黄色特征表示为不激活

(图片来源:作者)现在有三个特征方向:绿色、黄色和蓝色。我们的激活空间只有二维。

在这张第二张图中,我们增加了第三个特征方向:蓝色。因此,我们不能有一个向量,它使绿色特征激活,但蓝色特征不激活。间接地,任何沿着绿色方向的向量都会激活蓝色特征。

这通过绿色虚线表示,虚线显示了蓝色特征是如何从我们的绿色向量(本应只激活绿色特征)中“激活”的。

这就是为什么在 LLM 中特征如此难以解释的原因。 当数百万个特征都以叠加的方式表示时,解析哪些特征因其具有实际意义而激活,哪些仅仅因为干扰而激活(就像我们之前例子中的蓝色特征)非常困难。

稀疏自编码器(解决方案)

出于这个原因,我们使用了稀疏自编码器(SAE)。SAE 是一个简单的神经网络:由两个全连接层组成,中间有一个 ReLu 激活函数。

其思路如下:SAE 的输入是模型的激活值,SAE 试图在输出中重建这些相同的模型激活值。

SAE 是通过 LLM 中间层的输出进行训练的。它接收模型的激活值,映射到一个更高维度的状态,然后再映射回原始的激活值。

这引出了一个问题:如果输入和输出应该相同,那么 SAE 的意义何在?

答案是:我们希望第一层的输出能代表我们的特征。

因此,我们通过第一层增加维度(从激活空间映射到更大的维度)。这样做的目的是去除叠加,使得每个特征都能获得独立的正交方向。

我们还希望这个高维空间能够稀疏激活。也就是说,我们希望将每个激活点表示为少数几个向量的线性组合。这些向量理想情况下应该对应于我们输入中的最重要特征

因此,如果我们成功了,SAE 会将复杂的模型激活编码为一组稀疏且有意义的特征。如果这些特征是准确的,那么 SAE 的第二层应该能够将特征映射回原始激活值。

我们关心 SAE 第一层的输出——它是模型激活的稀疏特征编码

因此,当 Anthropic 基于激活空间中的方向来衡量特征的存在时,且在进行限幅操作使得某些特征变为激活或不激活时,他们是在 SAE 的隐藏状态上进行操作的。

在限幅的例子中,Anthropic 限制了SAE 第一层输出的特征,这些特征随后重建了略有不同的模型激活值。这些激活值继续通过模型的前向传播,并生成改变后的输出。

谁在乎?

我在文章开头引用了 James Harrington 的一句话。这个思想很简单:理解->控制->改进。这些都是我们对 LLMs 的非常重要的目标。

我们想要理解它们是如何构建世界观的,而可解释的特征作为方向似乎是我们理解它们的最佳方式。

我们希望能够对 LLMs 进行更精细的控制。能够在生成输出的过程中检测某些特征是否激活,并调节它们的激活程度,这是我们工具箱中一个非常有用的工具。

最后,从哲学的角度来看,我相信这对于提升LLM 的性能将变得重要。直到现在,情况并非如此。我们能够使 LLM 表现良好,而无需理解它们。

但我相信,随着改进的瓶颈逐渐显现,且大规模语言模型(LLM)变得越来越难以扩展,真正理解它们如何运作将变得至关重要,如果我们想在性能上实现下一次飞跃的话。

来源

[1] Adly Templeton, Tom Conerly, 扩展单一语义性:从 Claude 3 Sonnet 提取可解释特征, Anthropic

[2] Nelson Elhage, Tristan Hume, 叠加的玩具模型, Anthropic

[3] Tomas Mikolov, Wen-tau Yih, and Geoffrey Zweig, 连续空间词表示中的语言规律, Microsoft Research

可解释的 kNN(ikNN)

原文:towardsdatascience.com/interpretable-knn-iknn-33d38402b8fc?source=collection_archive---------3-----------------------#2024-05-14

一个可解释的分类器

W Brett KennedyTowards Data Science W Brett Kennedy

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

--

在机器学习中的分类或回归问题中,我们通常最关心的是获得最准确的模型。然而,在某些情况下,我们还对模型的可解释性感兴趣。虽然像 XGBoost、CatBoost 和 LGBM 这样的模型可能非常强大,但有时我们很难确定它们为什么做出某些预测,或者它们在面对未见过的数据时会如何表现。这些被称为黑箱模型,即我们并不完全理解它们为什么做出这些预测。

在许多情况下,这是可以接受的;只要我们知道它们在大多数情况下是相当准确的,它们就非常有用,而且大家理解它们偶尔会出现不准确的情况。例如,在一个网站上,我们可能有一个模型,用来预测哪些广告在展示给当前用户时最有可能产生销售。如果模型在少数情况下表现不佳,这可能会影响收入,但并不会造成重大问题;我们只不过有一个次优的模型,但通常是有用的。

但是,在其他一些情况下,了解模型做出预测的原因可能非常重要。这包括高风险环境,如医疗和安全领域。它还包括我们需要确保模型在种族、性别或其他受保护群体方面没有偏见的环境。它在审计环境中也很重要:在这些环境中,需要理解模型,以确保它们的表现符合预期。

即使在这些情况下,有时也可以使用黑箱模型(例如,提升模型、神经网络、随机森林等),然后进行所谓的事后分析。这种方法在事后提供了一个解释,说明为什么模型做出了这样的预测。这就是可解释人工智能(XAI)领域,它使用代理模型、特征重要性(例如 SHAP)、反事实推理或 ALE 图等技术。这些都是非常有用的工具,但在其他条件相同的情况下,最好还是有一个一开始就可解释的模型,至少在可能的情况下是这样。XAI 方法非常有用,但它们确实有局限性。

使用代理模型时,我们训练一个可解释的模型(例如,一个浅层决策树),让它学习黑箱模型的行为。这可以提供一定程度的解释,但并不总是准确的,只能提供近似的解释。

特征重要性也非常有用,但它们仅能指示哪些特征是相关的,并不能说明这些特征如何与预测相关,或者它们如何相互作用来形成预测。它们也无法判断模型在未见数据上的表现是否合理。

对于可解释模型,我们没有这些问题。模型本身是可以理解的,我们可以确切地知道它为何做出每个预测。然而,问题是:可解释模型的准确性可能低于黑箱模型。对于大多数问题来说,大多数可解释模型的表现无法与提升模型或神经网络竞争。对于任何给定的问题,可能需要尝试几个可解释模型,才能找到一个准确性足够的可解释模型,如果有的话。

今天有许多可解释模型,但不幸的是,选择非常有限。其中包括决策树、规则列表(和规则集)、GAMs(广义加性模型,如可解释提升机器)以及线性/逻辑回归。这些模型在适用的情况下是有用的,但选择有限。这意味着:对于许多项目来说,找到一个表现令人满意的可解释模型可能是不可能的。如果有更多的选择,可能会带来真正的好处。

我们在这里介绍另一个可解释的模型,称为 ikNN,或可解释的 k 最近邻。这个模型基于一组 2D kNN 模型。虽然这个想法非常直接,但它也出奇的有效,并且相当可解释。尽管它在准确性方面无法与最先进的表格数据预测模型(如 CatBoost)竞争,但它通常能提供接近的准确性,这对于某些问题来说已经足够。它在决策树和其他现有可解释模型中也具有较强的竞争力。

有趣的是,它的准确性似乎比普通的 kNN 模型更强。

项目的主页是:github.com/Brett-Kennedy/ikNN

该项目定义了一个名为 iKNNClassifier 的类。任何项目只需复制 interpretable_knn.py 文件并导入即可包含此类。它提供了与 scikit-learn 分类器一致的接口。也就是说,我们通常只需创建一个实例,调用 fit(),然后调用 predict(),类似于使用随机森林或其他 scikit-learn 模型。

在后台使用二维 kNN 集成方法提供了许多优势。其中一个是我们在集成方法中通常看到的优势:相比依赖单一模型,我们能够获得更可靠的预测。

另一个优势是二维空间易于可视化。该模型目前需要数值型输入(就像 kNN 模型一样),因此所有类别特征需要进行编码,但完成编码后,每个二维空间都可以像散点图一样进行可视化。这提供了较高的可解释性。

而且,可以确定每个预测最相关的二维空间,这使我们能够为每个记录展示少量的图表。这为每个记录提供了相当简单且完整的可视化解释。

因此,ikNN 是一个有趣的模型,因为它基于集成方法,但实际上提高了可解释性,而相反的情况更为常见。

标准 kNN 分类器

kNN 模型比许多其他模型使用得较少,因为它们通常不如提升模型或神经网络准确,也不如决策树具有可解释性。然而,它们仍然被广泛使用。它们基于一个直观的思想:一个项目的类别可以基于与其最相似的项目的类别来预测。

例如,如果我们查看鸢尾花数据集(如下例所示),我们有三类,代表三种鸢尾花。如果我们收集另一个鸢尾花样本并希望预测它属于哪一种鸢尾花,我们可以查看训练数据中最相似的 10 条记录,确定它们的类别,并选择这些记录中最常见的类别。

在这个例子中,我们选择 10 作为用于估计每条记录类别的最近邻数量,但也可以使用其他值。这作为超参数(k 参数)在 kNN 和 ikNN 模型中进行指定。我们希望设置 k 以使用合理数量的相似记录。如果使用的记录太少,结果可能不稳定(每个预测基于非常少的记录)。如果使用的记录太多,结果可能会基于一些不太相似的记录。

我们还需要一种方法来确定哪些是最相似的项目。为此,至少默认情况下,我们使用欧几里得距离。如果数据集有 20 个特征,并且我们使用 k=10,那么我们将根据它们的欧几里得距离在 20 维空间中找到最接近的 10 个点。

对于一条记录的预测,我们会找到训练数据中离它最近的 10 条记录,并查看它们的类别。如果 10 条记录中有 8 条是 Setosa 类别(鸢尾花的三种类型之一),那么我们可以假设这条记录很可能也是 Setosa,或者至少这是我们能做出的最佳猜测。

这个问题在于,当特征很多时,算法会出现问题,这就是所谓的维度灾难。高维空间的一个有趣特性是,当特征足够多时,点与点之间的距离开始变得没有意义。

kNN 还会平等地使用所有特征,尽管有些特征对目标的预测能力可能远远超过其他特征。点之间的距离是基于欧几里得(有时是曼哈顿或其他距离度量)计算的,考虑所有特征是相等的。这种方法简单,但并不总是最有效的,因为许多特征可能与目标无关。如果已经进行了特征选择,这种情况会有所减少,但特征的相关性仍然不等同。

而且,kNN 预测器做出的预测是不可解释的。算法本身非常直观,但预测结果可能难以理解。虽然可以列出任何记录的 k 个最近邻,这为预测提供了一些见解,但很难理解为什么某一组记录是最相似的,尤其是在特征很多的情况下。

ikNN 算法

ikNN 模型首先会取每一对特征,并使用这些特征创建一个标准的二维 kNN 分类器。所以,如果一个表格有 10 个特征,这将创建(10 选 2),即 45 个模型,每个模型对应一对独特的特征。

然后,模型会根据使用训练数据来预测目标列的准确度进行评估。基于这一点,ikNN 模型确定每个二维子空间的预测能力。在 45 个二维模型中,有些会比其他的更具预测力。为了进行预测,会使用已知最具预测力的二维子空间,且可以根据它们在训练数据上的预测能力进行加权。

此外,在推理时,可能会考虑每个二维空间中给定行的最近邻集的纯度,从而使模型能够更加重视那些在训练数据中证明最具预测性的子空间,以及那些在当前实例预测中表现出最高一致性的子空间。

考虑两个子空间和一个在这里显示为星形的点。在这两种情况下,我们都可以找到离该点最近的 k 个点。这里我们画了一个绿色圆圈围绕星形,尽管这些点集并不实际形成一个圆形(尽管第 k 个最近邻的半径实际上定义了一个邻域)。

这些图每个代表一对特征。在左侧的图中,星号邻居之间具有非常高的一致性:它们完全是红色的。在右侧的图中,邻居之间几乎没有一致性:有些是红色的,有些是蓝色的。第一对特征似乎比第二对特征更能预测记录,ikNN 正是利用了这一点。

这种方法使得模型能够考虑所有输入特征的影响,但以一种方式加权,使得更具预测性的特征的影响得到放大,而较少预测性的特征的影响则被削弱。

示例

我们首先使用一个玩具数据集来演示 ikNN,具体来说是鸢尾花数据集。我们加载数据,进行训练-测试集拆分,并在测试集上进行预测。

from sklearn.datasets import load_iris
from interpretable_knn import ikNNClassifier

iris = load_iris()
X, y = iris.data, iris.target

clf = ikNNClassifier()
X_train, X_test, y_train, y_test = train_test_split(
   X, y, test_size=0.33, random_state=42)
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)

对于预测,这是所需的一切。但 ikNN 还提供了理解模型的工具,具体来说是 graph_model()graph_predictions() API。

以下是 graph_model() 的示例:

ikNN.graph_model(X.columns)

这提供了数据空间的快速概览,默认情况下绘制了五个二维空间。点显示了训练数据的类别。背景颜色显示了 2D kNN 对每个二维空间区域所做的预测。

graph_predictions() API 将解释特定的行,例如:

这里,被解释的行(第 0 行)以红色星号显示。再次强调,默认情况下使用五个图,但为了简化,这里仅使用了两个。在这两个图中,我们可以看到第 0 行相对于训练数据的位置,以及 2D kNN 对该 2D 空间所做的预测。

可视化

虽然是可配置的,但默认情况下,每个 ikNN 预测仅使用五个二维空间。这确保了预测速度较快且可视化简单。这也意味着这些可视化展示了真实的预测,而不是对预测的简化,确保了预测的完全可解释性。

对于大多数数据集的大多数行,所有或几乎所有的二维空间在预测上趋于一致。然而,在预测不准确的地方,可能需要检查更多的二维图,以便更好地调节超参数,使其更适应当前的数据集。

准确性测试

使用 OpenML 上的 100 个分类数据集的随机集合进行了多组测试。在比较标准 kNN 和 ikNN 模型的 F1(宏观)得分时,ikNN 在 58 个数据集上得分较高,而 kNN 在 42 个数据集上得分较高。

ikNN 在执行网格搜索以寻找最佳超参数时,表现得更好。在对所有 100 个数据集的两个模型进行网格搜索后,ikNN 在 100 个案例中有 76 个表现最好。它还倾向于在训练和测试分数之间有较小的差距,表明它比标准 kNN 模型更加稳定。

ikNN 模型可能稍微慢一些,但它们通常比提升型模型快得多,而且依然非常快速,通常只需不到一分钟的训练时间,通常仅需几秒钟。

GitHub 页面提供了一些更多的示例和准确性的分析。

结论

虽然 ikNN 可能不是在准确性为主要目标时最强的模型(尽管像任何模型一样,它偶尔也能做到),但在需要可解释性模型的场合,它很可能是一个应该尝试的模型。

本页面提供了使用该工具所需的基本信息。只需下载 .py 文件(github.com/Brett-Kennedy/ikNN/blob/main/ikNN/interpretable_knn.py),将其导入到你的代码中,创建实例,进行训练和预测,并在需要时调用 graph_predictions() 来查看你希望查看的任何记录的解释。

所有图片均由作者提供。

可解释的潜在空间:使用填充空间的向量量化

原文:towardsdatascience.com/interpretable-latent-spaces-using-space-filling-vector-quantization-e4eb26691b14?source=collection_archive---------7-----------------------#2024-04-08

一种新的无监督方法,结合了向量量化和填充空间曲线两个概念,用于解释深度神经网络(DNNs)的潜在空间。

Mohammad Hassan ValiTowards Data Science Mohammad Hassan Vali

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

--

本文简要介绍了我们新提出的无监督分布建模技术——填充空间的向量量化[1],该方法在 2023 年 Interspeech 会议上发表。更多细节,请参阅此链接中的论文。

图片来自StockSnap.io

深度生成模型是著名的基于神经网络的架构,它们学习一个潜在空间,该空间中的样本可以映射到合理的现实世界数据,如图像、视频和语音。这些潜在空间像一个黑盒,通常难以解释。在本文中,我们介绍了我们新提出的无监督分布建模技术,它结合了填充空间曲线和向量量化(VQ)两个概念,这种方法被称为填充空间的向量量化(SFVQ)[1]。SFVQ 通过捕捉潜在空间的基本形态结构,帮助使潜在空间变得可解释。需要特别注意的是,SFVQ 是一个通用的分布建模工具,使用它并不局限于任何特定的神经网络架构,也不限于任何数据类型(例如图像、视频、语音等)。在本文中,我们演示了如何应用 SFVQ 来解释语音转换模型的潜在空间。要理解本文,您不需要具备语音信号的技术知识,因为我们将以通俗的方式解释一切(非技术性)。在开始之前,让我先解释一下 SFVQ 技术及其工作原理。

空间填充向量量化(SFVQ)

向量量化 (VQ) [2] 是一种数据压缩技术,类似于 k-means 算法,能够对任意数据分布进行建模。下图展示了应用于高斯分布的 VQ。VQ 使用 32 个代码本向量(蓝色点)或簇对该分布(灰色点)进行聚类。每个 Voronoi 单元(绿色线)包含一个代码本向量,确保该代码本向量是位于该 Voronoi 单元内所有数据点的最近代码本向量(在欧几里得距离的意义上)。换句话说,每个代码本向量是其对应 Voronoi 单元中所有数据点的代表向量。因此,应用 VQ 于此高斯分布意味着将每个数据点映射到其最近的代码本向量,即用其最近的代码本向量表示每个数据点。有关 VQ 及其其他变体的更多信息,您可以查看这篇文章。

在使用 32 个代码本向量的高斯分布上应用向量量化。(图片由作者提供)

空间填充曲线 是一种通过递归规则生成的分段连续线,如果递归迭代无限重复,曲线会弯曲,直到完全填充一个多维空间。下图展示了希尔伯特曲线 [3],这是一种著名的空间填充曲线,其角点在每次递归迭代中使用特定的数学公式定义。

填充 2D 正方形分布的希尔伯特曲线的前五次迭代。(图片由作者提供)

从空间填充曲线中获得直觉,我们可以将向量量化(VQ)视为将输入数据点映射到空间填充曲线(而不仅仅是将数据点映射到代码本向量,就像我们在普通 VQ 中所做的那样)。因此,我们将向量量化结合到空间填充曲线中,从而提出了我们的空间填充向量量化器(SFVQ)[1],通过连续的分段线性曲线对 D 维数据分布进行建模,其角点是向量量化的代码本向量。下图展示了 VQ 和 SFVQ 在高斯分布上的应用。

向量量化器的代码本向量(蓝色点)以及在高斯分布(灰色点)上应用的空间填充向量量化器(黑色曲线)。VQ 的 Voronoi 区域显示为绿色。(图片由作者提供)

关于如何训练 SFVQ 和如何将数据点映射到 SFVQ 曲线的技术细节,请参见我们论文 [1] 的第二部分。

请注意,当我们在一个分布上训练一个普通的 VQ 时,学习到的代码簿矩阵中的相邻代码簿向量可能表示完全不同的内容。例如,第一个代码簿元素可能表示一个元音音位,而第二个可能表示语音信号中的静音部分。然而,当我们在一个分布上训练 SFVQ 时,学习到的代码簿向量将以一种有序的形式排列,使得代码簿矩阵中相邻的元素(即相邻的代码簿索引)会表示分布中相似的内容。我们可以利用 SFVQ 的这一特性来解释和探索深度神经网络(DNNs)中的潜在空间。作为一个典型的例子,下面我们将解释如何使用 SFVQ 方法来解释语音转换模型[4]的潜在空间。

语音转换

下图展示了一个基于向量量化变分自编码器(VQ-VAE)[5]架构的语音转换模型[4]。根据该模型,编码器将说话人 A 的语音信号作为输入,并将输出传递到向量量化(VQ)模块,从中提取语音信号中的语音信息(音位)。然后,这些语音信息与说话人 B 的身份一起输入解码器,解码器输出转换后的语音信号。转换后的语音将包含说话人 A 的语音信息(上下文)以及说话人 B 的身份。

基于 VQ-VAE 架构的语音转换模型。(图像由作者提供)

在该模型中,VQ 模块充当信息瓶颈,学习语音的离散表示,仅捕获语音的语音内容并丢弃与说话人相关的信息。换句话说,VQ 代码簿向量被期望只收集语音的音位相关内容。在这里,VQ 输出的表示被视为该模型的潜在空间。我们的目标是用我们提出的 SFVQ 方法替代 VQ 模块,以解释潜在空间。通过解释,我们的意思是弄清楚每个潜在向量(代码簿向量)对应的音位是什么。

使用 SFVQ 解释潜在空间

我们评估了我们的空间填充向量量化器(SFVQ)在上面语音转换模型中寻找潜在空间(表示语音信息)结构的能力。为了进行评估,我们使用了 TIMIT 数据集[6],因为它包含了使用[7]中音位集标记的数据。对于我们的实验,我们使用了以下音位分组:

  • 爆破音(塞音):

  • 摩擦音:

  • 鼻音:

  • 元音:

  • 半元音(近似音):

  • 双元音:

  • 静音: {h#}。

为了分析我们提出的 SFVQ 的性能,我们分别将标记过的 TIMIT 语音文件通过训练好的编码器和 SFVQ 模块,并提取对应于语音中所有音素的码本向量索引。换句话说,我们将带有标记音素的语音信号传入,并计算出已学习的 SFVQ 码本向量的索引,查看这些音素映射到哪个位置。如上所述,我们期望我们的 SFVQ 将相似的语音内容映射到彼此相邻的位置(在学习过的码本矩阵中的索引位置)。为了验证这一期望,以下图示我们展示了句子“she had your dark suit”谱图,以及它在普通向量量化器(VQ)和我们提出的 SFVQ 下对应的码本向量索引。

(顶部) 使用我们提出的 SFVQ(深蓝色圆圈)和普通 VQ(灰色叉号)对语音信号的码本向量索引。 (底部) 包含与语音帧对应的码本向量索引的语音信号谱图。 (图片由作者提供)

我们观察到,普通 VQ 的索引没有任何特定结构。然而,当使用我们提出的 SFVQ 时,码本向量索引的结构变得非常清晰。包含摩擦音{sh, s}的语音帧(出现在单词{she, suit}中)在整个帧中均匀分布在彼此相邻的位置。此外,包含音素{h#}的静音帧和一些其他低能量帧(如单词{dark, suit}中的{ kcl, tcl: k, t closures})在 0–20 范围内均匀地排列在一起。请注意,以下图表对于具有相同语音内容的句子,在不同性别、语速和方言的说话者之间保持了一定的一致性。

下面的图展示了整个 TIMIT 语音文件中 SFVQ 的码本索引在每个语音组别(如上所述)上的直方图。乍一看,我们可以观察到辅音音素:{静音、爆破音、摩擦音、鼻音}和元音音素:{元音、双元音、近音}大约在索引 125 的位置被分开(除了索引 20 附近的峰值)。我们还注意到,不同组别的最显著峰值被分布在直方图的不同部分。

SFVQ 的码本向量索引在不同语音组别下的直方图。(图片由作者提供)

通过这种可视化,我们对潜在空间有了更好的理解,并且现在可以区分潜在空间中哪些部分对应于哪种语音组别。我们甚至可以进一步深入,按音素级别探索潜在空间。作为一个例子,以下图示为一个在摩擦音组中的所有音素分布的直方图。

SFVQ 代码本向量索引的直方图,针对摩擦音。(图片由作者提供)

通过观察每个音素的最显著峰值,我们发现相似音素的峰值是相邻的。具体来说,我们在这里列出了相似音素及其对应的峰值索引,如{f:51, v:50},{th:78, dh:80},{s:71, z:67, sh:65, zh:67},{hh:46, hv:50}。除了{hh, hv}音素外,摩擦音主要位于 50–85 的范围内。进一步的结构可以通过视觉检查从所有提供的图中轻松识别。

这些实验表明,我们提出的 SFVQ 能够实现对潜在代码本向量的连贯结构和易于解释的表示,这些向量表示输入语音的音素信息。因此,存在明显的音素分组区分,如{辅音与元音},{摩擦音与鼻音与双元音与……},并且我们可以简单地分辨出每个代码本向量所表示的音素。此外,特定音素组内的相似音素在潜在代码本空间中被编码在一起。这就是我们从一个被称为潜在空间的黑箱中希望获得的主要可解释性。

SFVQ 相比其他试图使潜在空间可解释的监督方法有一个优势,那就是 SFVQ 不需要任何人工标签和对学习潜在空间的手动限制。为了使我们的方法可解释,它只要求用户通过观察完全研究一次无监督学习的潜在空间。这一观察所需的标记数据比监督训练大模型所需的数据少得多。我们再次强调,SFVQ 是一个通用工具,用于建模分布,使用它并不局限于任何特定的神经网络架构或任何数据类型(例如图像、视频、语音等)。

GitHub 仓库

我们的 SFVQ 技术的 PyTorch 实现已公开,在 GitHub 上可以通过以下链接访问:

[## GitHub - MHVali/Space-Filling-Vector-Quantizer

通过在 GitHub 上创建帐户,为 MHVali/Space-Filling-Vector-Quantizer 的发展做出贡献。

github.com

致谢

特别感谢我的博士导师Prof. Tom Bäckström,他在这项工作中给予了我支持,并且是这项工作的另一位贡献者。

参考文献

[1] M.H. Vali, T. Bäckström,《利用空间填充曲线进行语音转换中音素分析的可解释潜在空间》,发表于Interspeech 会议论文集,2023 年。

[2] M. H. Vali 和 T. Bäckström,《NSVQ:用于机器学习的向量量化中的噪声替代》,IEEE Access,2022 年。

[3] H. Sagan,《空间填充曲线》,Springer 科学与商业媒体,2012 年*。

[4] B. Van Niekerk, L. Nortje 和 H. Kamper, “用于音频单元发现的向量量化神经网络——Zerospeech 2020 挑战”,收录于Interspeech 会议论文集,2020 年。

[5] A. Van Den Oord, O. Vinyals 和 K. Kavukcuoglu, “神经离散表示学习”,收录于第 31 届国际神经信息处理系统会议论文集,2017 年。

[6] J. S. Garofolo, L. F. Lamel, W. M. Fisher, J. G. Fiscus, D. S. Pallett 和 N. L. Dahlgren, “DARPA TIMIT 声学-语音连续语音语料库 CDROM”,语言数据联盟,1993 年。

[7] C. Lopes 和 F. Perdigao, “在 TIMIT 数据库上进行音素识别”,收录于语音技术。IntechOpen,2011 年,第十四章。[在线]。可用链接:doi.org/10.5772/17600

可解释的异常值检测:频繁模式异常值因子(FPOF)

原文:towardsdatascience.com/interpretable-outlier-detection-frequent-patterns-outlier-factor-fpof-0d9cbf51b17a?source=collection_archive---------0-----------------------#2024-05-25

一种支持分类数据并为标记为异常值的数据提供解释的异常值检测方法

W Brett KennedyTowards Data Science W Brett Kennedy

·发布于Towards Data Science ·阅读时间 10 分钟·2024 年 5 月 25 日

--

异常值检测是机器学习中的一个常见任务。具体来说,它是一种无监督机器学习:分析没有标签的数据。它是从数据集中找到相对于其他数据项而言非常不寻常的项目。

在数据中识别异常值可能有多种原因。如果所检查的数据是会计记录,且我们有兴趣查找错误或欺诈,数据中的交易通常太多,无法逐一手动检查,因此需要选择少量可管理的交易进行调查。一个好的起点是找到最不寻常的记录并进行检查;这一做法的假设是错误和欺诈应该足够罕见,以至于能够作为异常值突出显示。

也就是说,并非所有异常值都是有趣的,但错误和欺诈很可能是异常值,因此在寻找这些时,识别异常值可以是一个非常实用的技巧。

或者,数据可能包含信用卡交易、传感器读数、天气测量、生物数据或网站日志。在所有这些情况下,识别可能表示错误或其他问题的记录,以及最有趣的记录,都非常有用。

异常值检测通常也作为商业或科学发现的一部分,用于更好地理解数据以及数据中描述的过程。例如,在科学数据中,我们通常对找到最不寻常的记录感兴趣,因为这些记录可能是最具科学价值的。

异常值检测中的可解释性需求

对于分类和回归问题,通常更倾向于使用可解释的模型。这样可能会导致较低的准确性(对于表格数据,通常使用提升模型能够获得最高的准确性,而这些模型通常是难以解释的),但也更安全:我们知道模型如何处理未见过的数据。但在分类和回归问题中,通常也不需要理解为何做出个别的预测。只要模型的准确性足够高,简单地让模型进行预测可能就足够了。

然而,对于异常值检测来说,对可解释性的需求要高得多。当异常值检测器预测某一记录非常不寻常时,如果我们不清楚为什么会这样,我们可能不知道该如何处理这个项,甚至是否应该相信它是异常的。

实际上,在许多情况下,如果没有很好地理解为什么某些被标记为异常值的项被标记为异常,进行异常值检测可能会有很有限的价值。如果我们正在检查一个信用卡交易数据集,并且异常值检测程序识别出一系列看起来非常不寻常的购买,因而认为这些购买是可疑的,我们只能有效地调查这些异常值,前提是我们知道这些异常之处在哪里。在某些情况下,这可能是显而易见的,或者在花些时间审查之后会变得清楚,但如果异常的性质从一开始就能明确,那么这样会更有效率和效果。

与分类和回归一样,当无法解释时,通常可以尝试通过所谓的事后解释(post-hoc explanations)来理解预测结果。这些解释使用了 XAI(可解释人工智能)技术,如特征重要性、代理模型、ALE 图等。这些方法也非常有用,未来的文章中会进一步讨论。但在一开始就拥有清晰的结果也是非常有利的。

本文专门讨论表格数据,但将在后续文章中讨论其他形式的数据。目前,常用的表格数据异常值检测算法有多个,包括 Isolation Forests、局部异常因子(LOF)、KNN、单类支持向量机(One-Class SVM)等。这些算法通常表现良好,但不幸的是,大多数算法并不提供对找到的异常值的解释。

大多数异常值检测方法在算法层面上容易理解,但要确定为什么某些记录被检测器评分较高而其他记录没有,仍然是很困难的。如果我们使用例如 Isolation Forest 处理一个财务交易数据集,我们可以看到哪些记录是最不寻常的,但可能无法理解其原因,特别是当数据表有许多特征,异常值包含多个特征的罕见组合,或者异常值是那些没有单一特征特别不寻常,但多个特征中等不寻常的情况。

频繁模式异常值因子(FPOF)

我们现在已经快速浏览了异常检测和可解释性的内容。本文其余部分摘自我写的《Python 中的异常检测》一书(www.manning.com/books/outlier-detection-in-python),专门讲解 FPOF。

FPOF(FP-outlier: Frequent pattern based outlier detection)是少数几种能够为异常检测提供某种程度可解释性的检测器之一,值得在异常检测中得到更多的应用。

它还有一个吸引人的特点,就是它是专门设计用于处理类别型数据,而不是数值型数据的。大多数现实世界中的表格数据是混合型的,包含了数值型和类别型列。但大多数检测器假设所有列都是数值型的,这就需要将所有类别型列进行数值编码(使用独热编码、顺序编码或其他编码方式)。

当像 FPOF 这样的检测器假设数据是类别型时,我们遇到的问题恰恰相反:所有数值型特征必须进行分箱,才能转化为类别型格式。这两种方式都可行,但当数据主要是类别型时,能够使用像 FPOF 这样的检测器非常方便。

在进行异常检测时,拥有一些数值型检测器和一些类别型检测器是非常有益的。不幸的是,类别型检测器相对较少,因此 FPOF 在这方面也很有用,即使在不需要可解释性的情况下。

FPOF 算法

FPOF 通过识别表格中所谓的频繁项集(FISs)来工作。这些项集可以是某一特征中非常常见的值,也可以是跨越多个列、经常一起出现的值集合。

几乎所有表格都包含大量的频繁项集。基于单一值的频繁项集会出现在某些列中的某些值显著比其他值更常见的情况下,这几乎是永远成立的。基于多个列的频繁项集会出现在这些列之间存在关联的情况下:某些值(或数值范围)往往与其他列中的值(或数值范围)关联。

FPOF 的核心思想是,只要一个数据集包含许多频繁项集(几乎所有数据集都有),那么大多数行将包含多个频繁项集,而正常(内点)记录将包含比异常行更多的频繁项集。我们可以利用这一点,将异常行识别为那些包含的频繁项集远少于大多数行的记录。

使用真实数据的示例

在使用 FPOF 的真实世界示例中,我们来看一下 OpenML 上的 SpeedDating 数据集(www.openml.org/search?type=data&sort=nr_of_likes&status=active&id=40536,遵循 CC BY 4.0 DEED 协议)。

执行 FPOF 从挖掘数据集中的频繁项集开始。Python 中有许多库可以支持这一操作。在这个示例中,我们使用 mlxtend(rasbt.github.io/mlxtend/),这是一个通用的机器学习库。它提供了几种算法来识别频繁项集;我们在这里使用了其中一种名为 apriori 的算法。

我们首先从 OpenML 收集数据。通常我们会使用所有的分类特征和(分箱的)数值特征,但为了简化起见,在这里我们只使用少量特征。

如前所述,FPOF 确实需要对数值特征进行分箱。通常,我们会对每个数值列使用少量(大约 5 到 20 个)等宽的分箱。pandas 的 cut() 方法在这方面非常方便。这个示例甚至更简单,因为我们只处理分类列。

from mlxtend.frequent_patterns import apriori
import pandas as pd
from sklearn.datasets import fetch_openml
import warnings

warnings.filterwarnings(action='ignore', category=DeprecationWarning)

data = fetch_openml('SpeedDating', version=1, parser='auto') 
data_df = pd.DataFrame(data.data, columns=data.feature_names)

data_df = data_df[['d_pref_o_attractive', 'd_pref_o_sincere',
                   'd_pref_o_intelligence', 'd_pref_o_funny',
                   'd_pref_o_ambitious', 'd_pref_o_shared_interests']] 
data_df = pd.get_dummies(data_df) 
for col_name in data_df.columns:
    data_df[col_name] = data_df[col_name].map({0: False, 1: True})

frequent_itemsets = apriori(data_df, min_support=0.3, use_colnames=True) 

data_df['FPOF_Score'] = 0

for fis_idx in frequent_itemsets.index: 
    fis = frequent_itemsets.loc[fis_idx, 'itemsets']
    support = frequent_itemsets.loc[fis_idx, 'support'] 
    col_list = (list(fis))
    cond = True
    for col_name in col_list:
        cond = cond & (data_df[col_name])

    data_df.loc[data_df[cond].index, 'FPOF_Score'] += support   

min_score = data_df['FPOF_Score'].min() 
max_score = data_df['FPOF_Score'].max()
data_df['FPOF_Score'] = [(max_score - x) / (max_score - min_score) 
                         for x in data_df['FPOF_Score']]

apriori 算法要求所有特征都必须进行独热编码。为此,我们使用 pandas 的 get_dummies() 方法。

然后,我们调用 apriori 方法来确定频繁项集。在这个过程中,我们需要指定最小支持度,即频繁项集出现的最小行数比例。我们不希望这个值太高,否则记录,即使是强内点,也会包含很少的频繁项集,从而难以与异常值区分开。我们也不希望这个值太低,否则频繁项集可能没有意义,而且异常值和内点可能包含相同数量的频繁项集。较低的最小支持度也可能导致 apriori 生成大量的频繁项集,导致执行变慢并降低可解释性。在这个例子中,我们使用 0.3。

也可以,且有时会,设置 FIS 的大小限制,要求它们与某些最小和最大列数相关,这有助于缩小你最感兴趣的异常值形式。

然后,频繁项集以 pandas 数据框的形式返回,其中包含支持度和列值列表(以独热编码列的形式,指示原始列和值)。

为了解释结果,我们可以首先查看频繁项集,下面展示了这一部分。为了包括每个频繁项集的长度,我们添加了:

frequent_itemsets['length'] = \
    frequent_itemsets['itemsets'].apply(lambda x: len(x))

找到了 24 个频繁项集,最长的涵盖了三个特征。下表展示了前十行,按支持度排序。

然后,我们遍历每个频繁项集,并通过支持度为每一行中包含该频繁项集的记录增加得分。这个过程可以选择性地调整,以偏向更长的频繁项集(因为支持度为 0.4 且覆盖 5 列的频繁项集,相较于支持度为 0.4 且覆盖 2 列的频繁项集,在其他条件相同的情况下,更具相关性),但在此我们仅使用每行中频繁项集的数量和支持度。

这实际上产生了一个关于正常性而非异常性的得分,因此当我们将得分标准化为介于 0.0 和 1.0 之间时,我们会反转顺序。得分最高的行现在是最强的异常值:那些包含最少和最少常见频繁项集的行。

将得分列添加到原始数据框并按得分排序后,我们可以看到最正常的行:

我们可以看到这一行的值与 FIS 匹配得很好。d_pref_o_attractive 的值是 [21–100],这是一个 FIS(支持度 0.36);d_pref_o_ambitiousd_pref_o_shared_interests 的值分别是 [0–15] 和 [0–15],这也是一个 FIS(支持度 0.59)。其他值也倾向于匹配 FIS。

接下来展示的是最不寻常的行。该行与任何已识别的 FIS 都不匹配。

由于频繁项集本身非常易于理解,这种方法具有产生合理可解释结果的优势,尽管当使用多个频繁项集时这一点的适用性较差。

可解释性可能会降低,因为异常值是通过不包含 FIS 来识别的,而不是通过包含 FIS,这意味着解释一行的得分就等于列出它没有包含的所有 FIS。然而,解释每个异常值时,并不严格需要列出所有缺失的 FIS;列出一小组最常见的缺失 FIS 就足以为大多数目的提供合理的异常值解释。关于存在的 FIS 及其在行中出现的正常数值和频率的统计数据,能够为比较提供良好的背景。

这种方法的一种变体是使用不频繁的项集,而不是频繁的项集,通过每一行所包含的不频繁项集的数量和稀有度来对其进行评分。这也可以产生有用的结果,但计算开销要大得多,因为需要挖掘更多的项集,并且每一行都要与多个 FIS 进行测试。尽管如此,最终的得分可能更具可解释性,因为它们是基于每一行中找到的项集,而不是缺失的项集。

结论

除了这里的代码外,我不知道 Python 中有 FPOF 的实现,尽管 R 中有一些实现。FPOF 的主要工作是挖掘 FIS,而有许多 Python 工具可以用来进行这一操作,包括这里使用的 mlxtend 库。剩余的 FPOP 代码,如上所示,相对简单。

鉴于异常检测中可解释性的重要性,FPOF 很可能值得尝试。

在未来的文章中,我们还将介绍一些其他可解释的异常检测方法。

所有图像均由作者提供

机器学习中的权重正则化解释

原文:towardsdatascience.com/interpreting-weight-regularization-in-machine-learning-99f2677f7ef5?source=collection_archive---------7-----------------------#2024-08-23

为什么 L1 和 L2 正则化会导致模型稀疏和权重收缩?L3 正则化又会怎样呢?继续阅读,了解更多!

Dhruv MataniTowards Data Science Dhruv Matani

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

--

图片来自 D koiUnsplash

Naresh Singh 合著。

介绍

阅读本文后,您将具备足够的工具和推理能力来思考任何 Lk 正则化项的效果,并决定它是否适用于您的情况。

机器学习中的正则化是什么?

让我们先看看互联网上的一些定义,并基于这些定义进行概括。

  1. 正则化是一组减少机器学习模型过拟合的方法。通常,正则化通过牺牲训练准确度的微小下降来换取模型泛化能力的提高。 (IBM)

  2. 正则化使得模型在不同的数据子集上保持稳定。它减少了模型输出对训练集细微变化的敏感度。 (geeksforgeeks)

  3. 机器学习中的正则化是一种防止模型过拟合的方法。 (simplilearn)

通常,正则化是一种防止模型过拟合的技术,使模型能够在未见过的数据上进行泛化预测。接下来,我们特别讨论一下权重正则化的作用。

为什么使用权重正则化?

在训练机器学习模型时,可以采用多种形式的正则化。权重正则化就是一种技术,本文将重点讨论它。权重正则化意味着对机器学习模型中可学习的权重施加一些约束,以使它们能够使模型对未见过的输入具有较好的泛化能力。

权重正则化通过惩罚节点的权重矩阵来提高神经网络的性能。这种惩罚抑制了模型拥有大参数(权重)值的情况。它有助于控制模型拟合训练数据噪声的能力。通常,机器学习模型中的偏置项不受正则化约束。

在深度神经网络中,正则化是如何实现的?

通常,正则化损失会在训练过程中添加到模型的损失中。它允许我们在训练过程中控制模型的权重。公式如下所示:

图-1:总损失是模型损失和正则化损失的总和。k 是一个浮动值,表示正则化范数。Alpha 是正则化损失的加权因子。

实践中常用的 k 值是 1 和 2。这些被称为 L1 和 L2 正则化方案。

但是,为什么我们大多数时候只使用这两个值,事实上 k 还有无数个可能的值?让我们通过对 L1 和 L2 正则化方案的解释来回答这个问题。

不同权重正则化类型的解释

在机器学习模型中,最常用的两种正则化类型是 L1 和 L2 正则化。我们将从这两种开始,并继续讨论一些不常见的正则化类型,如 L0.5 和 L3 正则化。我们将查看正则化损失的梯度,并将其绘制出来,以直观地理解它们如何影响模型的权重。

L1 正则化

L1 正则化通过将权重的绝对值的平均值加起来作为正则化损失。

图-2:L1 正则化损失及其对每个权重 Wi 的偏导数。

它通过一个常数(在此情况下为 alpha 乘以学习率)来调整权重,方向是最小化损失的方向。图 3 显示了该函数及其导数的图示。

图-3:蓝线表示 |w|,红线表示 |w| 的导数。

你可以看到,L1 范数的导数是常数(取决于 w 的符号),这意味着该函数的梯度仅取决于 w 的符号,而不取决于其大小。L1 范数在 w=0 时的梯度是未定义的。

这意味着在每次反向传播步骤中,权重将通过一个常数值向零移动。在整个训练过程中,它有助于将权重收敛到零。这就是为什么 L1 正则化会使模型变得稀疏(即一些权重变为 0)。在某些情况下,如果它使模型过于稀疏,可能会导致问题。L2 正则化则没有这种副作用。我们将在下一节讨论这一点。

L2 正则化

L2 正则化将权重绝对值的平方的平均值加总作为正则化损失。

图 4:L2 正则化损失及其相对于每个权重 Wi 的偏导数。

它有助于在使损失最小化的方向上,通过权重本身的倍数调整每个权重。图 5 展示了该函数及其导数的图形表示。

图 5:蓝线是 pow(|w|, 2),红线是 pow(|w|, 2) 的导数。

你可以看到,L2 范数的导数只是该范数本身的符号调整后的平方根。L2 范数的梯度依赖于权重的符号和大小。

这意味着在每次梯度更新步骤中,权重将根据权重值的大小按比例调整到接近零。随着时间的推移,这会将权重拉向零,但永远不会完全为零,因为从一个值中减去常数因子本身,除非该值本来就为零,否则结果永远不会精确为零。L2 范数通常用于机器学习模型训练中的权重衰减。

让我们接下来考虑 L0.5 正则化。

L0.5 正则化

L0.5 正则化将权重绝对值的平方根的平均值加总作为正则化损失。

图 6:L0.5 正则化损失及其相对于每个权重 Wi 的偏导数。

这意味着在每次反向传播步骤中,权重将通过一个常数倍(在此情况下为 alpha 乘以学习率)调整,调整的方向是使损失最小化,并且调整量是权重自身的平方根的倒数。图 7 展示了该函数及其导数的图形。

图 7:蓝线是 pow(|w|, 0.5),红线是 pow(|w|, 0.5) 的导数。

你可以看到,L0.5 范数的导数是一个不连续的函数,在接近零的正值 w 处达到峰值,并且在接近零的负值 w 处达到负无穷大。此外,我们可以从图中得出以下结论:

  1. 当|w|趋近于 0 时,梯度的大小趋向于无穷大。在反向传播过程中,这些 w 值会迅速跳过 0,因为大的梯度会导致 w 值发生剧烈变化。换句话说,负的 w 将变为正,反之亦然。这种反转的循环会持续重复。

  2. 当|w|增加时,梯度的大小会减小。这些 w 值是稳定的,因为梯度较小。然而,每一次反向传播步骤后,w 的值都会被拉向 0。

这种情况几乎不是人们希望从权重正则化过程中得到的结果,因此可以安全地说 L0.5 不是一个好的权重正则化器。接下来我们考虑 L3 正则化。

L3 正则化

L3 正则化将权重绝对值的立方的平均值作为正则化损失进行求和。

图 8:L3 正则化损失及其相对于每个权重 Wi 的偏导数。

这会通过一个倍数(在这种情况下是 alpha 倍学习率)调整每个权重的平方值,朝着最小化损失的方向进行调整。

从图形上看,这就是函数及其导数的样子。

图 9:蓝线表示 pow(|w|, 3),红线表示 pow(|w|, 3)的导数。

为了真正理解这里发生了什么,我们需要放大 w=0 点附近的图表。

图 10:蓝线表示 pow(|w|, 3),红线表示 pow(|w|, 3)的导数,放大显示 w 值接近 0.0 时的情况。

可以看到,L3 范数的导数是一个连续且可微的函数(尽管导数中有|w|),它在大值 w 时具有较大的幅度,而在小值 w 时具有较小的幅度。

有趣的是,对于非常小的 w 值,接近 0.0 时,梯度几乎为零。

L3 的梯度解释很有趣。

  1. 对于较大的 w 值,梯度的大小很大。在反向传播过程中,这些值将被推向 0。

  2. 一旦权重 w 达到拐点(接近 0.0),梯度几乎消失,权重将停止更新。

其效果是将大幅度的权重拉近 0,但不会完全变为 0。

让我们考虑更高的范数,看看在极限情况下会发生什么。

超越 L3 正则化

要理解 Linfinity 的情况,我们需要看看 L10 正则化的情况。

图 11:蓝线表示 pow(|w|, 10),红线表示 pow(|w|, 10)的导数,放大显示 w 值接近 0.0 时的情况。

可以看出,当|w| < 0.5 时,梯度非常小,这意味着对于这些 w 值,正则化将不会起作用。

练习

基于我们上述的所有观察,L1 和 L2 正则化根据你想要实现的目标是相当实用的。作为一个练习,尝试推理一下 L1.5 正则化的行为,其图表如下所示。

图 12:蓝线是 pow(|w|, 1.5),红线是 pow(|w|, 1.5) 的导数。

结论

我们从视觉和直观的角度审视了 L1 和 L2(以及一般的 Lk)正则化项,以理解为什么 L1 正则化会导致稀疏的模型权重,而 L2 正则化会使模型权重接近 0。在本次练习中,将解决方案框定为检查结果的梯度是非常有价值的。

我们探索了 L0.5、L3 和 L10 正则化项,并以图形方式呈现,你(读者)推理了 L1 和 L2 正则化之间的正则化项,并形成了对它们对模型权重影响的直观理解。

我们希望本文能够为你在模型训练和微调时考虑正则化策略时提供更多的技巧。

本文中的所有图表均使用在线 Desmos 图形计算器创建。这是一个链接,供你在需要时玩弄这些函数。

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

参考文献

在研究这一主题时,我们发现以下文章非常有用,我们希望你也能从中受益!

  1. Stackexchange 讨论

  2. TDS: 解密 L1 和 L2 正则化(第三部分)

  3. L1 和 L2 正则化的视觉解释

  4. Ian Goodfellow 的《深度学习》

  5. Gareth James 的《统计学习简介》

相交多个 3D 线段(最近点)

原文:towardsdatascience.com/intersect-multiple-3d-lines-closest-point-93e06b296f15?source=collection_archive---------11-----------------------#2024-03-27

寻找一批 3D 线段的交点,更像是一个最小化问题,而不是一个实际的交点测试,正如通常与两条光线做的那样。

Thomas RouchTowards Data Science Thomas Rouch

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

--

摄影师:Baudouin Wisselmann 来自 Unsplash

1. 引言

动机

了解多个 3D 线段相交的位置在 3D 重建或增强现实等领域非常有用。例如,它帮助我们通过多视角的 2D 图像检测来三角化一个 3D 点,或者找到相机围绕物体旋转时的主要 3D 关注点。

尽管每一行可能包含一些噪音,但拥有更多的行有助于减少变异性,就像我们计算多个样本的平均值一样。

两条光线 VS 多条光线

在之前的一篇文章中,相交 3D 光线(最近点),我解释了如何使一对 3D 光线相交。但是,如果我们想让 n 条光线相交呢?正如我们在本文中所看到的,从两条光线到 n 条光线不仅仅是一个推广,它在本质上是不同的。

两条光线相交 VS 多条光线相交 — 图由作者提供

提出一个问题本身就意味着这个问题值得被问。因此,寻找一组 3D 光线的交点,本质上暗示着这样一个共同的汇聚点的存在,即使有一些噪音。

快速提醒:从数学角度看,射线由其原点*o*和方向*d*定义。由于它是 1 维空间,射线上的任何点都可以通过与原点的正距离(或时间)*t*来进行参数化。

在两条射线的情况下,我们求解每条射线在最接近的点处的参数化t1t2,然后从中判断射线是否相交,即t1>0t2>0。由于最优的t1t2可以独立计算,这种方法也允许提前停止,例如如果t1<0,则跳过t2的计算。

n条射线的情况下,我们可以假设射线相交,并直接求解 3D 交点,而不是求解每条射线上的n个最优参数化。因此,这更像是一个最小化问题,而不是一个实际的交点测试。

射线还是直线?

由于我们没有检查每条射线上的参数化,更准确地说,应该讨论直线而非射线;因此,文章的标题为直线。直线就是没有t约束的射线。

图片由Ben Wicks提供,来自Unsplash

2. 解析解

有用的向量技巧

在深入讨论主题之前,我想快速讨论一个对我们之后非常有用的向量技巧。

通过点积来缩放向量也可以看作是矩阵-向量乘积。由于乘积运算符的结合性,项确实可以重新排列。

这将帮助我们通过突出变量c的线性关系来因式分解方程。

最小化问题

交点可以定义为 3D 点x,它最小化与每条输入射线的平方距离之和。

点到直线的正交投影

从射线原点o到点x的向量可以表示为两个正交向量的和。如下面的图所示,第一个向量沿着直线从op,而第二个向量从px

一旦我们知道这个点p的坐标,即x的投影,我们就可以通过计算向量x-p的范数来推导出点x到直线的距离。

点-直线距离 — 作者插图

让我们计算p

如下面的方程组所示,点p由两个关键属性定义:它属于直线(具有未知射线参数tp),并且x-p与直线正交。

将射线参数化引入第二个方程中,可以得到tp的值,它是x-od的点积。

注意:我们假设射线方向d已经被归一化。

我们现在可以将其代回射线参数化,来恢复点p

前面提到的向量技巧在这里很有用,可以强调关于x-o的线性关系,这将有助于因式分解。

点-线距离

如前所述,点-线距离现在可以通过计算x-p的范数来得到。注意到x-o中的优美因式分解。

损失函数

点-线距离公式为我们提供了我们想要最小化的损失函数L

x-o中的因式分解使得我们可以很容易地推导出它相对于x的梯度。

现在可以通过确定梯度为零的点来获得这个凸损失函数的最小值。

最终我们得到一个线性3x3方程组Ax=b,其中矩阵A和向量b的定义如下:

因此,最接近所有射线的 3D 点可以通过求逆A来获得。

结论

最小二乘法最小化通常可以简化为一个普通的正态方程,其中伪逆矩阵用于将n个观测值聚合成一个方阵可逆矩阵。

值得一提的是,我们在这里并不需要伪逆,因为矩阵的平方形状自然地来源于射线方向外积的和。

希望你喜欢阅读这篇文章,并且它能为你提供更多关于如何求交多条 3D 射线的见解!

DSPy 介绍:告别提示,迎接编程!

原文:towardsdatascience.com/intro-to-dspy-goodbye-prompting-hello-programming-4ca1c6ce3eb9?source=collection_archive---------0-----------------------#2024-02-27

DSPy 框架通过将提示替换为编程和编译,解决了基于 LLM 的应用中的脆弱性问题

Leonie MonigattiTowards Data Science Leonie Monigatti

·发布于Towards Data Science ·13 分钟阅读·2024 年 2 月 27 日

--

DSPy(图像由作者手绘)

目前,使用大型语言模型(LLMs)构建应用程序不仅复杂,而且脆弱。典型的管道通常使用提示来实现,而提示是通过反复试验手工制作的,因为LLMs 对如何被提示非常敏感。因此,当你更改管道中的某个部分,比如 LLM 或数据时,性能很可能会减弱——除非你适应提示(或微调步骤)。

当你更改管道中的某个部分时,比如 LLM 或数据,性能很可能会变弱……

DSPy [1] 是一个框架,旨在通过优先考虑编程而非提示,解决基于语言模型(LM)应用中的脆弱性问题。它允许你重新编译整个管道,优化它以适应你的特定任务——而不是每次更改一个组件时都重复手工编写提示工程。

尽管关于该框架的论文 [1] 已于 2023 年 10 月发布,我最近才了解它。在短短的……

使用 Langchain 介绍 LLM 代理:当 RAG 不足以满足需求时

原文:towardsdatascience.com/intro-to-llm-agents-with-langchain-when-rag-is-not-enough-7d8c08145834?source=collection_archive---------0-----------------------#2024-03-15

AI 助手的脑结构一阶原则

Alex HoncharTowards Data Science Alex Honchar

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

--

大家好,本文是我两周前与Neurons Lab进行的教程的书面形式。如果你更喜欢叙述性引导,可以在这里找到 YouTube 视频:

和往常一样,你可以在GitHub 上找到代码,这里还有独立的 Colab 笔记本:

  1. 规划与推理

  2. 不同类型的记忆

  3. 各种类型的工具

  4. 构建完整的代理

代理介绍

图示由作者提供。LLM 通常通过 RAG 架构增强外部记忆。代理将这一概念扩展到记忆、推理、工具、答案和行动。

让我们通过探索各种 LLM 代理的示例来开始这堂讲座。虽然这个话题被广泛讨论,但实际上很少有在积极使用代理的;我们通常认为是代理的,往往只是大型语言模型。我们可以考虑一个简单的任务——搜索足球比赛结果并将其保存为 CSV 文件。我们可以比较几种现有的工具:

  • 带有搜索和插件的 GPT-4:正如你在这里的聊天历史中看到的,GPT-4 因代码错误未能完成任务。

  • AutoGPT 通过evo.ninja/至少可以生成某种形式的 CSV(尽管不理想):

由于现有的工具不太理想,让我们从构建代理的基本原理开始学习。我正在使用丽莉安的博客文章作为结构参考,但同时加入了更多我自己的例子。

步骤 1:规划

简单的“输入-输出”LLM 使用和像思维链、带自一致性的思维链、思维树等技术之间的视觉差异

你可能遇到过各种旨在提升大型语言模型性能的技术,例如提供建议或甚至开玩笑地威胁它们。一种流行的技术叫做“思维链”,在这种方法中,模型被要求一步步思考,从而实现自我修正。这种方法已经发展成更高级的版本,如“带自一致性的思维链”和广义的“思维树”,其中多个思维被创造、重新评估并整合,以提供输出

在本教程中,我重度使用Langsmith,这是一个将 LLM 应用生产化的平台。例如,在构建思维树提示时,我将我的子提示保存在提示仓库中并加载它们:

from langchain import hub
from langchain.chains import SequentialChain

cot_step1 = hub.pull("rachnogstyle/nlw_jan24_cot_step1")
cot_step2 = hub.pull("rachnogstyle/nlw_jan24_cot_step2")
cot_step3 = hub.pull("rachnogstyle/nlw_jan24_cot_step3")
cot_step4 = hub.pull("rachnogstyle/nlw_jan24_cot_step4")

model = "gpt-3.5-turbo"

chain1 = LLMChain(
    llm=ChatOpenAI(temperature=0, model=model),
    prompt=cot_step1,
    output_key="solutions"
)

chain2 = LLMChain(
    llm=ChatOpenAI(temperature=0, model=model),
    prompt=cot_step2,
    output_key="review"
)

chain3 = LLMChain(
    llm=ChatOpenAI(temperature=0, model=model),
    prompt=cot_step3,
    output_key="deepen_thought_process"
)

chain4 = LLMChain(
    llm=ChatOpenAI(temperature=0, model=model),
    prompt=cot_step4,
    output_key="ranked_solutions"
)

overall_chain = SequentialChain(
    chains=[chain1, chain2, chain3, chain4],
    input_variables=["input", "perfect_factors"],
    output_variables=["ranked_solutions"],
    verbose=True
)

你可以在这个笔记本中看到这种推理的结果,我想在这里强调的是定义推理步骤和在像 Langsmith 这样的 LLMOps 系统中进行版本控制的正确过程。另外,你还可以在像 ReAct 或 Self-ask with search 这样的公开仓库中看到其他流行的推理技术示例:

prompt = hub.pull("hwchase17/react")
prompt = hub.pull("hwchase17/self-ask-with-search")

其他值得注意的方法有:

  • 反思Shinn & Labash 2023)是一个框架,通过动态记忆和自我反思能力来装备代理,从而提高推理技能。

  • 后见之明链(CoH;Liu et al. 2023)鼓励模型通过明确地展示一系列过去的输出,每个输出都附有反馈,来改进其自身的输出。

步骤 2:记忆

我们可以将大脑中不同类型的记忆映射到 LLM 代理架构的各个组件上。

  • 感官记忆: 记忆的这一部分捕捉即时的感官输入,比如我们看到、听到或感受到的东西。在提示工程和 AI 模型的上下文中,提示作为一种瞬时输入,类似于短暂的触摸或感觉。它是触发模型处理的初步刺激。

  • 短期记忆: 短期记忆暂时保存信息,通常与当前的任务或对话相关。在提示工程中,这等同于保留最近的聊天记录。这种记忆使得代理能够保持上下文和连贯性,确保响应与当前对话一致。在代码中,通常将其作为对话历史添加

from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain.agents import AgentExecutor
from langchain.agents import create_openai_functions_agent

llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
tools = [retriever_tool]
agent = create_openai_functions_agent(
    llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

message_history = ChatMessageHistory()
agent_with_chat_history = RunnableWithMessageHistory(
    agent_executor,
    lambda session_id: message_history,
    input_messages_key="input",
    history_messages_key="chat_history",
)
  • 长期记忆: 长期记忆储存事实性知识和过程性指令。在 AI 模型中,这通过用于训练和微调的数据来表示。此外,长期记忆支持 RAG 框架的运作,允许代理访问并将学到的信息整合到它们的响应中。它就像是代理用来生成有根据和相关输出的综合知识库。在代码中,通常将其作为向量化数据库添加
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import WebBaseLoader
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings

loader = WebBaseLoader("https://neurons-lab.com/")
docs = loader.load()
documents = RecursiveCharacterTextSplitter(
    chunk_size=1000, chunk_overlap=200
).split_documents(docs)
vector = FAISS.from_documents(documents, OpenAIEmbeddings())
retriever = vector.as_retriever()

第 3 步:工具

在实际应用中,你可能希望通过独立的推理线路增强你的代理(这可以是另一个 LLM,例如领域特定的 LLM,或者是用于图像分类的其他 ML 模型),或者通过一些更基于规则或 API 的方式来增强。

ChatGPT 插件OpenAI API 函数调用是 LLM 增强工具使用能力在实际中工作的良好示例。

  • 内置 Langchain 工具: Langchain 提供了一个内置工具库,包括互联网搜索、Arxiv 工具包、Zapier 和 Yahoo Finance 等。对于这个简单的教程,我们将尝试使用Tavily提供的互联网搜索功能:
from langchain.utilities.tavily_search import TavilySearchAPIWrapper
from langchain.tools.tavily_search import TavilySearchResults

search = TavilySearchAPIWrapper()
tavily_tool = TavilySearchResults(api_wrapper=search)

llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0.0)
agent_chain = initialize_agent(
    [retriever_tool, tavily_tool],
    llm,
    agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True,
)
  • 自定义工具: 定义自己的工具也非常简单。让我们分析一个计算字符串长度的简单工具示例。你需要使用@tool装饰器让 Langchain 知道它的存在。然后,不要忘记输入和输出的类型。但最重要的部分是""" """之间的函数注释 —— 这是你的代理知道这个工具做什么的方式,并且会将这个描述与其他工具的描述进行比较:
from langchain.pydantic_v1 import BaseModel, Field
from langchain.tools import BaseTool, StructuredTool, tool

@tool
def calculate_length_tool(a: str) -> int:
    """The function calculates the length of the input string."""
    return len(a)

llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0.0)
agent_chain = initialize_agent(
    [retriever_tool, tavily_tool, calculate_length_tool],
    llm,
    agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True,
)

你可以在这个脚本中找到它是如何工作的示例,但你也可以看到一个错误 —— 它没有提取到正确的 Neurons Lab 公司描述,尽管调用了正确的自定义长度计算函数,但最终结果还是错误的。让我们尝试修复它!

步骤 4:所有内容合并

我提供了一个干净的版本,将所有架构的部分组合在一起,请参见这个脚本。注意,我们如何轻松地将其分解并分别定义:

  • 各种工具(搜索、自定义工具等)

  • 各种记忆感官作为提示,短期作为可运行的消息历史,以及作为提示中的草图板,长期作为从向量数据库中检索的内容)

  • 任何类型的规划策略(作为从 LLMOps 系统提取的提示的一部分)

代理的最终定义看起来将是如此简单:

llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
agent = create_openai_functions_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
agent_with_chat_history = RunnableWithMessageHistory(
    agent_executor,
    lambda session_id: message_history,
    input_messages_key="input",
    history_messages_key="chat_history",
)

如你在脚本输出中看到的(或者你也可以自己运行它),它解决了前面部分中与工具相关的问题。发生了什么变化?我们定义了一个完整的架构,其中短期记忆起着至关重要的作用。我们的代理获得了消息历史和草图板作为推理结构的一部分,这使得它能够提取正确的网站描述并计算其长度。

结束语

我希望这次对 LLM 代理架构核心元素的讲解能帮助你设计出能够自动化认知任务的功能性机器人。在此,我再次强调,确保代理的所有元素都到位的重要性。正如我们所看到的,缺失短期记忆或工具描述不完整,会干扰代理的推理,甚至导致非常简单任务(如摘要生成和长度计算)给出错误答案。祝你的 AI 项目好运,如果你在公司遇到问题,欢迎随时联系我

介绍 ft-Q:通过特征级量化提升向量压缩

原文:towardsdatascience.com/introducing-ft-q-improving-vector-compression-with-feature-level-quantization-3c18470ed2ee?source=collection_archive---------6-----------------------#2024-11-26

量化

通过特征级量化(ft-Q)推动量化的极限

Michelangiolo MazzeschiTowards Data Science Michelangiolo Mazzeschi

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

--

***要理解本文,需具备 嵌入 基本量化 的知识。该算法的实现已在 GitHub 发布,并且完全开源。

更新 24–20–12: 我注意到一种类似的方法已经存在,并且可以在句子转换器库中通过 标定嵌入 (请引用正确的作者)。然而,当应用于二值量化时,依然是错误的,因为应该使用 中位数 来将分布分为两个完全相等的部分

自从大规模语言模型(LLM)问世以来,量化已成为生产环境中节省内存的最流行技术之一。很快,它也在向量数据库中得到普及,向量数据库开始使用相同的技术压缩不仅是模型,还有用于检索目的的向量。

在本文中,我将展示当前量化算法的局限性,并提出一种新的量化方法(ft-Q)来解决这些问题。

什么是量化,它是如何工作的?

量化是一种节省内存的算法,允许我们使用更少的位数存储数字(无论是内存中还是磁盘上)。默认情况下,当我们在内存中存储任何数字时,我们使用 float32:这意味着该数字是通过 32 位(二进制元素)组合存储的。

例如,整数 40 在 32 位对象中的存储方式如下:

将数字 40 存储在 32 位对象中,图像来源:作者

然而,我们可以决定使用更少的位来存储相同的数字(将内存使用量减少一半),例如使用 16 位对象:

将数字 40 存储在 16 位对象中,图像来源:作者

通过量化,我们指的是使用较少的位数存储数据(例如,32 -> 16,或 32 -> 4),这也称为转换。如果我们存储 1GB 的数字(默认存储为 32 位对象),如果我们决定使用 16 位对象来存储它们(从而应用量化),我们的数据大小将减半,结果是 0.5GB。

量化是否存在某些陷阱?

节省如此多的存储空间看起来令人难以置信(正如你所理解的,我们可以不断减少,直到达到最小的位数:1 位,也就是二进制量化。我们的数据库大小将减少 32 倍,从 1GB 减少到 31.25MB!),但是,正如你可能已经理解的那样,这其中有一个陷阱。

任何数字都可以存储在所有可能组合的位数允许的范围内。使用 32 位量化时,你可以存储最多 2³²个数字。有这么多可能的组合,以至于我们决定在使用 32 位时包括小数。例如,如果我们在初始数字中添加一个小数并将 40.12 存储在 32 位中,它将使用这种 1 和 0 的组合:

01000010 00100000 01111010 11100001

我们已经理解,在 32 位存储下(由于它的巨大组合可能性),我们几乎可以编码每个数字,包括其小数点(为了澄清,如果你对量化不熟悉,实数和小数点是没有分开的,40.12 作为一个整体会被转换成 32 个二进制数的组合)。

如果我们不断减少位数,所有可能的组合会呈指数级减少。例如,4 位存储的组合数限制为 2⁴:我们只能存储 16 个数字(这没有太多空间来存储小数)。在 1 位存储中,我们只能存储一个数字,要么是 1,要么是 0。

为了让这一点更具实际意义,将我们的初始 32 位数字存储为二进制代码将迫使我们将所有数字(例如 40.12)转换为 0 或 1。在这种情况下,这种压缩方式看起来并不理想。

如何最大化量化的效果

我们已经看到了量化如何导致信息丢失。那么,究竟如何利用它呢?当你看一个单一数字的量化(例如将 40.12 转换为 1),似乎从如此极端的量化中没有任何价值可言,因为损失实在是太大了。

然而,当我们将这一技术应用于一组数据(例如向量)时,信息丢失并不像应用于单一数字时那么剧烈。向量搜索是一个非常适合以有用方式应用量化的例子。

当我们使用编码器,例如all-MiniLM-L6-v2时,我们将每个样本(原本是文本形式)存储为一个向量:一个由 384 个数字组成的序列。正如你可能已经理解的那样,存储数百万个相似的序列是非常昂贵的,我们可以通过量化大幅减少原始向量的大小。

也许,将我们的向量从 32 位量化到 16 位并不会带来太大的损失。但如果是 4 位甚至二进制量化呢?由于我们的数据集相对较大(每个有 384 个数字),这种复杂性使我们能够在不造成过多检索损失的情况下达到更高的压缩水平。

4 位量化

我们执行量化的方式是观察我们展平后的向量的数据分布,并选择映射到一个具有更少位数的等效区间。我最喜欢的例子是 4 位量化。在这种复杂度下,我们可以存储 2⁴ = 16 个数字。但是,正如所解释的那样,我们向量中的所有数字都是复杂的,每个数字都有多个小数点:

array([ 2.43655406e-02, -4.33481708e-02, -1.89688837e-03, -3.76498550e-02,
       -8.96364748e-02,  2.96154656e-02, -5.79943173e-02,  1.87652372e-02,
        1.87771711e-02,  6.30387887e-02, -3.23972516e-02, -1.46128759e-02,
       -3.39277312e-02, -7.04369228e-03,  3.87261175e-02, -5.02494797e-02,
        ...
       -1.03239892e-02,  1.83096472e-02, -1.86534156e-03,  1.44851031e-02,
       -6.21072948e-02, -4.46912572e-02, -1.57684386e-02,  8.28376040e-02,
       -4.58770394e-02,  1.04658678e-01,  5.53084277e-02, -2.51113791e-02,
        4.72703762e-02, -2.41811387e-03, -9.09169838e-02,  1.15215247e-02],
      dtype=float32)

我们可以做的是将分布中的每个数字映射到一个区间,该区间的范围是[-8, 7](16 个可能的数字)。为了定义区间的极端值,我们可以使用我们正在量化的分布的最小值和最大值。

4 位量化:灰色区域是整数区间[-8, 7],不要将其与位数混淆。该区间中的任何数字将会被转换成一个 4 位的对象,图片来源:作者

例如,分布的最小值/最大值为[-0.2, 0.2]。这意味着-0.2 将被转换为-8,0.2 将被转换为 7。分布中的每个数字都会有一个量化后的等效值(例如,示例数组中的第一个数字(0.02436554)将被量化为 0,如上图所示)。

array([[-1, -3, -1, ...,  1, -2, -2],
       [-6, -1, -2, ..., -2, -2, -3],
       [ 0, -2, -4, ..., -1,  1, -2],
       ...,
       [ 3,  0, -5, ..., -5,  7,  0],
       [-4, -5,  3, ..., -2, -2, -2],
       [-1,  0, -2, ..., -1,  1, -3]], dtype=int4)

1 位量化

相同的原理也适用于二进制量化,但它要简单得多。规则如下:分布中小于 0 的每个数字变为 0,大于 0 的每个数字变为 1。

1 位量化,图片来源:作者

不是所有的嵌入(embeddings)都是一样构建的

当前量化技术的主要问题是,它们假设我们所有的值基于单一的分布。因此,当我们使用阈值来定义区间(例如最小值和最大值)时,我们只使用从数据总集派生出来的单一集合(这个集合是基于单一分布建模的)。

展平编码数据集的所有单个样本的分布,图片来源:作者

在一项实验中,我将 41,963 个游戏描述编码成了向量通过观察每个特征的数据分布,我们可以看到尽管作出了努力,仍然没有任何一个特征是完全标准化的:其均值可能偏离目标值 0。

编码数据集中 20 个随机特征的分布,图像来自作者

简而言之,每个特征都可以用专用分布来建模。因为数据不遵循单一的巨大分布,我们可以通过在特征层应用量化,利用这种组织方式。此外,嵌入通常会使用相似的值来编码每个特征(否则均值将始终为 0),这意味着编码额外数据时漂移的可能性最小

为了更好地解释数学内容,先定义两组值:

S = 编码数据集中的所有独立样本(41936 * 384)

Fₙ = 编码数据集中属于单一特征的所有独立样本(41936 * 1)

特征 29:丑小鸭

在我们的样本数据集中,每个向量包含 384 个特征。然而,通过逐一查看数据,我们可以注意到某些特征并未完美标准化,而是有显著偏斜。以F₂₉为例:下图展示了F₂₉(41936)在整个编码数据集中的分布。

F₂₉分布,图像来自作者

从图中可以看到,它的分布均值约为-0.07,边缘值为(-0.2, 0.1)。我确信,基于对编码器行为的了解,无论我们输入多少额外数据,F₂₉始终会保持一个“丑小鸭”,其分布不会改变。该分布只包含少量正值。

常规量化

现在,让我们对书本应用二进制量化,但仅对F₂₉进行。之所以选择二进制方法,是因为大部分信息会丢失,意味着可以通过不同的方法进行改进。

要以二进制方式量化值,我们需要选择一个值作为阈值,将值转换为 0 或 1。最简单的方法是选择 0(约为S的分布均值)。在处理F₂₉的值时,由于大多数值为负数,大多数值会被量化为 0,只有少数会被量化为 1。

应该量化为 1 但量化为 0 的样本:F₂₉的 44%,图像来自作者

让我们进一步探索数据:94%的F₂₉已转换为 0,而在一个完美标准化的分布中,我们的目标是 50%。这意味着F₂₉的 44%(密度分布的红色区域)没有被正确量化。

# we count the number of 0 over the total number of values
>>> 1-quantized_regular[:, 29].sum()/sample_vectors[:, 29].size
0.9424122472338802 

ft-量化

如果我们不使用 0 作为阈值(从S中提取),而是将F₂₉分布作为基准呢?再次查看F₂₉分布,除了 0,我们将使用其均值~ -0.07,并将其极值作为区间的最小/最大值~ [-0.20, 0.10](请参见下方图像:对图像不准确表示歉意,极值还是有的)。简单来说,ft-Q 将参考量化区间的位置进行调整,以更好地适应数据的真实分布。

ft-Q 的可视化,区间已调整以适应特征分布,图片来源:作者

通过以下文章,我试图介绍一种新算法,据我所知,至今没有在其他地方找到类似的算法。请注意,该算法不同于用于训练神经网络的 FQ(特征量化),这是一种在训练后使用的算法。

我欢迎批评期待任何反馈

在对F₂₉应用二进制量化后,由于阈值已更新,我们可以看到数据有一半会被量化为 0,另一半为 1,从而更真实地表示数据。通过比较量化结果,ft-Q 将F₂₉的 47%转换为 0,只有 3%的值未被正确量化。

# we count the number of 0 over the total number of values
>>> 1-quantized_tfQ[:, 29].sum()/sample_vectors[:, 29].size
0.46809423884013734

总结来说,ft-Q(或 ft-量化)将每个特征单独编码,从而最小化由于非标准化分布所可能产生的错误。

何时使用 ft-量化

从实际情况来看,没有任何嵌入是完全标准化的,特征分布中会存在一些变异(尽管是微小的)。然而,现在我们已经识别出错误的位置,我们可以使用以下方法进行调整。

ft-Q

ft-Q 可以应用于常规嵌入吗?

当 ft-Q 应用于常规嵌入时,我们不会看到显著的增强效果。

>>> err_regular = .5-quantized_regular.sum()/sample_vectors.size
>>> err_ftQ = .5-quantized_tfQ.sum()/sample_vectors.size
>>> err_total = abs(err_regular)-abs(err_ftQ)
>>> err_total
0.012901293538566672

all-MiniLM-L6-v2的情况下,我们取得了1.2%的提升(虽然不显著,但仍然是一次升级)。

ft-Q 的亮点:处理过的嵌入

然而,嵌入并不总是以标准化形式使用。有时,在某些应用场景中,需要对嵌入进行处理(例如,在covariate encoding的情况下)。我们可以使用以下理论图表来理解在哪些情况下ft-Q 能够更好地发挥作用:

特征的偏斜程度越大,ft-Q 的效果越明显,图片来源:作者

经过额外处理步骤得到的向量不一定是标准化的:我们可以重新标准化它们,然后再应用量化,但通过使用 ft-Q 作为单一操作(即使在标准化不完美的情况下仍有小幅提升),我们可以一举两得。

结论

总之,本文试图提出一种更细致的量化方法。最初,开发该算法的目的是解决处理后的嵌入向量的性能问题,但经过适当的实验后,它在常规场景中也证明了其有效性。

随着大型语言模型(LLM)和更复杂的向量数据库的普及,内存管理和性能优化在信息检索领域变得越来越重要,因此我们有责任熟悉这些内容,并提出新的、更好的解决方案。

时间将证明,是否会有新的、更智能的数据压缩方法加入其中。至于现在,你可以充分利用该算法。

介绍层增强分类(LEC)

原文:towardsdatascience.com/introducing-layer-enhanced-classification-lec-4972f4f1c79f?source=collection_archive---------1-----------------------#2024-12-20

一种使用剪枝语言模型的轻量级安全分类新方法

Tula MastermanTowards Data Science Tula Masterman

·发布于 Towards Data Science ·阅读时间:10 分钟·2024 年 12 月 20 日

--

利用来自中间 Transformer 层的隐藏状态进行高效且强大的内容安全和提示注入分类

作者和 GPT-4o 的图片,旨在表示大型语言模型提供的强大语言理解能力。

介绍

随着语言模型(LM)的广泛应用,检测用户输入和语言模型生成输出中的不当内容变得越来越重要。每当主要模型提供商发布新模型时,人们首先尝试做的事情之一就是寻找方法来“越狱”或以其他方式操控模型,使其做出不该做出的回应。通过 Google 或 X 快速搜索,可以看到许多人找到绕过模型对齐调优的方法,使得模型回应不当的请求。此外,许多公司已经公开发布了基于生成性 AI 的聊天机器人,用于客户服务等任务,这些机器人经常遭受提示注入攻击,并回应不合适的任务,甚至远远超出其预期的使用范围。检测和分类这些实例对于企业来说至关重要,以防它们部署的系统被用户轻易操控,尤其是在将聊天系统公开部署时。

我的团队,Mason SawtellSandi BesenJim Brown最近发布了我们的论文使用剪枝语言模型的轻量级安全分类,作为 ArXiv 预印本。我们的工作介绍了一种新方法——层增强分类(LEC),并证明使用 LEC 可以通过利用语言模型中间变换层的隐藏状态来训练一个带有极少可训练参数(最低为 769 个)和少量训练示例(通常少于 100 个)的带惩罚的逻辑回归分类器,有效地分类内容安全违规和提示注入攻击。这种方法将简单分类模型的计算效率与语言模型的强大语言理解相结合。

所有使用我们的方法 LEC 训练的模型,都比为每个任务专门设计的特殊模型以及 GPT-4o 更具优势。我们发现,存在一些最优的中间变换层,可以为内容安全和提示注入分类任务提供必要的特征。这一点很重要,因为它表明你可以使用同一个模型同时进行内容安全违规、提示注入分类,并生成输出标记。另外,你也可以使用一个非常小的语言模型,将其剪枝至最优的中间层,并使用该层的输出作为分类任务的特征。这将允许实现一个极为计算高效且轻量的分类器,能够与现有的语言模型推理流水线很好地集成

这是我计划分享的关于此主题的几篇文章中的第一篇。在本文中,我将总结我们研究的目标、方法、主要结果和意义。在未来的文章中,我计划分享我们如何将这种方法应用到 IBM 的 Granite-8B 模型和一个没有任何保护措施的开源模型上,从而使两个模型能够在一次通过模型的过程中同时检测内容安全和提示注入违规行为,并生成输出标记。有关我们研究的更多细节,欢迎查看完整论文,阅读我同事Sandi Besen 的文章,或随时联系我们提问。

目标与方法

概述: 我们的研究重点是理解在作为分类任务输入特征时,中间 Transformer 层的隐藏状态表现如何。我们希望了解,如果我们能够为任务识别出最优的层,而不是使用整个模型/最后一层进行分类,那么小型通用模型和专用模型在内容安全和提示注入分类任务上是否会表现得更好。我们还希望理解,在任务的起始点上,模型的总参数量最小能达到什么程度。其他研究表明,模型的不同层专注于任何给定提示输入的不同特征,我们的研究发现,中间层通常能够最准确地捕捉对这些分类任务最重要的特征。

数据集: 对于内容安全和提示注入分类任务,我们比较了使用我们的方法训练的模型与基准模型在特定任务数据集上的表现。先前的研究表明,在几百个样本后,我们的分类器仅会看到小幅的性能提升,因此在两个分类任务中,我们使用了一个包含 5000 个随机抽样样本的任务特定数据集,这样可以在最大限度降低计算和训练时间的同时,确保数据的多样性。对于内容安全数据集,我们使用了来自 OpenSafetyLab 的SALAD 数据和来自 LMSYS 的LMSYS-Chat-1M数据集的组合。对于提示注入数据集,我们使用了SPML数据集,因为它包含了系统和用户提示对。这一点至关重要,因为一些用户请求可能看起来“安全”(例如,“帮我解这个数学题”),但实际上它们要求模型做出超出系统提示定义的意图范围的回答(例如,“你是公司 X 的智能助手,只能回答关于我们公司的问题”)。

模型选择: 我们使用 GPT-4o 作为两个任务的基准模型,因为它被广泛认为是最强大的 LLM 之一,并且在某些情况下,超越了基准的专用模型。对于内容安全分类任务,我们使用 Llama Guard 3 1B 和 8B 模型;而对于提示注入分类任务,我们使用 Protect AI 的 DeBERTA v3 Base Prompt Injection v2 模型,因为这些模型被认为在各自领域中处于领先地位。我们将我们的方法 LEC 应用于基准专用模型(Llama Guard 3 1B、Llama Guard 3 8B 和 DeBERTa v3 Base Prompt Injection)和通用模型。对于通用模型,我们选择了 Qwen 2.5 Instruct,尺寸为 0.5B、1.5B 和 3B,因为这些模型的规模与专用模型较为接近。

该设置使我们能够比较三个关键内容:

  1. 我们的方法在应用于小型通用模型时的表现如何,相较于基线模型(GPT-4o 和特殊用途模型)。

  2. 应用我们的方法相对于特殊用途模型自身基线性能对任务的改善程度。

  3. 我们的方法如何在不同模型架构中泛化,通过评估其在通用模型和

    特殊用途模型。

重要的实现细节: 对于 Qwen 2.5 Instruct 模型和任务特定的特殊用途模型,我们修剪了各个层并捕获了变换器层的隐藏状态,以训练带有 L2 正则化的惩罚逻辑回归(PLR)模型。PLR 模型的可训练参数数量与模型隐藏状态的大小相同,再加上二元分类任务中的偏差项,这一数值从最小模型(Protect AI 的 DeBERTa)的 769 个,到最大模型(Llama Guard 3 8B)的 4097 个。我们使用不同数量的样本训练分类器,以帮助我们理解各个层对任务的影响,以及需要多少样本才能超越基线模型的性能或在 F1 分数上达到最佳表现。我们将整个测试集通过基线模型,以确定它们在每个任务上的表现。

作者和团队展示的高层次 LEC 训练过程的图像。训练样本独立地通过模型,捕获每个变换器层的隐藏状态。这些隐藏状态随后用于训练分类器。每个分类器都使用不同数量的样本进行训练。结果使我们能够确定哪些层产生了与任务相关的特征,以及为了实现最佳性能需要多少样本。

关键结果

在本节中,我将分别介绍两项任务——内容安全分类和提示注入分类——的重要结果。

两项任务的主要发现:

  1. 总体而言,我们的方法在所有评估的任务、模型和训练样本数量中,通常能在 20 到 100 个样本之间超越基线模型,获得更高的 F1 分数。

  2. 当在较少的样本上进行训练时,中间层相比于最终层在 F1 分数上通常会显示出最大的提升。这些层也通常在相对于基线模型的性能上表现最好。这表明,重要的局部特征在变换器网络的早期就被表示出来,并且表明,样本较少的应用场景尤其能从我们的方法中受益。

  3. 此外,我们发现将我们的方法应用于特殊用途模型,能够超越模型自身的基线性能,通常在 20 个样本内,通过识别并利用最相关的任务层。

  4. 使用我们的方法,通用的 Qwen 2.5 Instruct 模型和任务特定的专用模型在较少示例下都能获得更高的 F1 分数。这表明,我们的方法能够跨架构和领域进行推广。

  5. 在 Qwen 2.5 Instruct 模型中,我们发现中间模型层在较少的示例下能获得更高的 F1 分数,无论是内容安全分类任务还是提示注入分类任务。这表明,使用一个模型同时处理这两类分类任务并一次性生成输出是可行的。考虑到分类器的体积较小,这些额外的分类步骤所需的计算时间几乎可以忽略不计。

内容安全分类结果:

由作者和团队提供的图片展示了 LEC 在 Qwen 2.5 Instruct 0.5B、Llama Guard 3 1B 和 Llama Guard 3 8B 模型中,针对二分类内容安全任务在选定层的表现。x 轴显示训练示例数量,y 轴反映加权 F1 分数。

  1. 对于二分类和多分类任务,使用我们的方法训练的通用和专用模型通常在 20 个示例内超越基准的 Llama Guard 3 模型,在不到 100 个示例内超越 GPT-4o 模型。

  2. 对于二分类和多分类任务,通用和专用的 LEC 模型通常在中间层的表现超过所有基准模型,甚至可能在所有层上都优于基准模型。我们在二分类内容安全任务中的结果,超越了基准模型,取得了最大 F1 分数 0.95 或 0.96,适用于 Qwen 2.5 Instruct 和 Llama Guard LEC 模型。相比之下,GPT-4o 的基准 F1 分数为 0.82,Llama Guard 3 1B 为 0.65,Llama Guard 3 8B 为 0.71。

  3. 对于二分类任务,我们的方法在应用于 Qwen 2.5 Instruct 0.5B、Llama Guard 3 1B 和 Llama Guard 3 8B 时,表现相当。模型分别达到了最大 F1 分数 0.95、0.96 和 0.96。有趣的是,Qwen 2.5 Instruct 0.5B 在 15 个示例内超越了 GPT-4o 的基准性能,而 Llama Guard 3 的两个模型则需要 55 个示例才能做到这一点。

  4. 对于多分类任务,使用 Qwen 2.5 Instruct 0.5B 中间层的隐藏状态训练的一个非常小的 LEC 模型,在 35 个训练示例内就超越了 GPT-4o 的基准性能,适用于多分类任务的所有三个难度级别。

提示注入分类结果:

由作者和团队提供的图片展示了 LEC 在 Qwen 2.5 Instruct 0.5B 和 DeBERTa v3 Prompt Injection v2 模型中,针对提示注入分类任务在选定层的表现。x 轴显示训练示例数量,y 轴反映加权 F1 分数。这些图表展示了两种 LEC 模型在中间模型层上,如何在最少的训练示例下超越基准模型。

  1. 将我们的方法应用于通用 Qwen 2.5 Instruct 模型和专用 DeBERTa v3 Prompt Injection v2,结果显示,两个模型的中间层在不到 100 个训练示例的情况下超过了基准模型的表现。这再次表明我们的方法可以跨模型架构和领域进行推广。

  2. 所有三种 Qwen 2.5 Instruct 模型在 5 个训练示例内,超过了基准 DeBERTa v3 Prompt Injection v2 模型的 F1 得分 0.73。

  3. Qwen 2.5 Instruct 0.5B 在 55 个示例中超过了 GPT-4o 的中间层(第 12 层)表现。对于更大的 Qwen 2.5 Instruct 模型,观察到类似但略有更好的表现。

  4. 将我们的方法应用于 DeBERTa v3 Prompt Injection v2 模型,结果达到了最高的 F1 得分 0.98,显著超过了该模型在此任务中的基准 F1 得分 0.73。

  5. 中间层在 DeBERTa 模型和所有 Qwen 2.5 Instruct 模型尺寸中实现了最高的加权 F1 得分。

结论

在我们的研究中,我们集中研究了两个与负责任的 AI 相关的分类任务,但我们预计此方法也适用于其他分类任务,前提是任务的关键特征可以通过模型的中间层检测到。

我们证明了通过在中间 Transformer 层的隐藏状态上训练分类模型,可以创建有效的内容安全和提示注入分类模型,且具有最少的参数和训练示例。此外,我们还展示了我们的方法如何提高现有专用模型的性能,相较于它们自身的基准结果。

我们的结果表明,集成顶级内容安全性和提示注入分类器到现有 LLM 推理工作流中有两个有前景的选项。一个选项是采用像我们论文中探讨的轻量级小型模型,修剪到最优层,并将其用作分类任务的特征提取器。然后,可以使用该分类模型在处理用户输入之前,利用像 GPT-4o 这样的闭源模型,识别任何内容安全违规或提示注入。相同的分类模型还可以在将生成的响应发送给用户之前,验证其有效性。第二个选项是将我们的方法应用于一个开源的通用模型,如 IBM 的 Granite 或 Meta 的 Llama 模型,识别哪些层对分类任务最相关,然后更新推理管道,以在生成输出响应的同时同时进行内容安全性和提示注入分类。如果检测到内容安全或提示注入违规,可以轻松停止输出生成;否则,如果没有违规,模型可以继续生成响应。这两种选项都可以扩展到基于 AI 代理的场景,具体取决于每个代理所使用的模型。

总结来说,LEC 提供了一种新的、有前景的实际解决方案,通过识别内容安全性和提示注入攻击,以比现有方法更好的性能和更少的训练样本来保障基于生成式 AI 的系统。这对任何今天在使用生成式 AI 构建系统的个人或企业来说至关重要,以确保他们的系统既负责任地运行,又能按预期工作。

注意:本文和研究论文中表达的观点仅代表作者个人意见,并不一定反映其所属雇主的观点或政策。

有兴趣进一步讨论或合作吗?请通过 LinkedIn联系我们!

附加参考资料:

介绍马尔可夫决策过程,设置 Gymnasium 环境并通过动态规划方法求解

原文:towardsdatascience.com/introducing-markov-decision-processes-setting-up-gymnasium-environments-and-solving-them-via-e806c36dc04f?source=collection_archive---------4-----------------------#2024-08-26

通过理查德·S·萨顿的《强化学习》一书和自定义的 Python 实现,解析“强化学习”,第二集

Oliver STowards Data Science Oliver S

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

--

上一篇文章 中,我们开始了关于强化学习(RL)系列的第一篇文章,依据萨顿的经典著作[1]。在那篇文章中,我们介绍了强化学习的一般概念,并讨论了多臂赌博机问题,这是一个非关联的玩具问题。

在这里,我们将在此基础上进一步扩展——但会有显著的提升。特别是,我们将引入我们的第一个关联问题,这对许多读者来说可能更像是“真实”的强化学习(RL)——并介绍一种简单但通用的解决技巧。此外,我们还将介绍 Gymnasium [2],这是一个强大的库,提供了多种环境(例如,Atari 或 MuJoCo 游戏),并允许我们快速实验解决这些问题。

图片来源:Adarsh KummurUnsplash

前面提到的关联设置是强化学习(RL)中的“标准”:与之前介绍的非关联设置不同,后者只有一个状态,我们只需决定采取什么行动,而在这里,我们有多个状态——对于每个状态,我们可能会选择不同的最佳行动。

介绍 n 步时间差分方法

原文:towardsdatascience.com/introducing-n-step-temporal-difference-methods-7f7878b3441c?source=collection_archive---------1-----------------------#2024-12-29

剖析理查德·S·萨顿的《强化学习》,附带定制的 Python 实现,第五集

Oliver STowards Data Science Oliver S

·发表于Towards Data Science ·阅读时间 10 分钟·6 天前

--

在我们之前的文章中,我们通过探索时间差分(TD)学习总结了强化学习(RL)技术的基础系列。TD 方法结合了动态规划(DP)和蒙特卡罗(MC)方法的优点,利用它们的最佳特性形成了一些最重要的 RL 算法,如 Q 学习。

基于这一基础,本文深入探讨了n 步 TD 学习,这是萨顿在其书籍第七章中介绍的一个多功能方法[1]。该方法弥合了经典 TD 和 MC 技术之间的差距。像 TD 一样,n 步方法使用自举(利用先前的估计),但它们还结合了接下来的n个奖励,提供了短期和长期学习的独特结合。在未来的文章中,我们将通过资格迹进一步概括这一概念。

我们将采用一种结构化方法,从预测问题开始,然后转向控制。在这个过程中,我们将:

  • 介绍n 步 Sarsa

  • 将其扩展到离策略学习

  • 探索n 步树备份算法,并

  • 提出一个统一的视角,使用n 步 Q(σ)

像往常一样,您可以在GitHub上找到所有配套代码。让我们开始吧!

介绍 NumPy,第一部分:理解数组

原文:towardsdatascience.com/introducing-numpy-part-1-understanding-arrays-3f6fecc97e3d?source=collection_archive---------5-----------------------#2024-09-10

快速成功的数据科学

创建、描述和访问属性

Lee VaughanTowards Data Science Lee Vaughan

·发布于 Towards Data Science ·阅读时长 19 分钟·2024 年 9 月 10 日

--

DALL-E3 想象的 3D 数组

欢迎来到 介绍 NumPy 系列,这是一个面向 Python(或 NumPy)初学者的四部分教程。目标是通过展示 NumPy 的核心功能,配合表格和实际操作示例,帮助读者理解 NumPy。

NumPy,简称 Numerical Python,是 Python 用于数值计算的基础库。它扩展了 Python 的数学能力,并为许多科学和数学包奠定了基础。

NumPy 增强了 Python 标准库 中的内置工具,而这些工具对许多数据分析计算来说可能过于简单。使用 NumPy,您可以执行快速操作,包括数学运算、排序、选择、I/O、离散傅里叶变换、基本线性代数、基本统计操作、形状操作、随机模拟等。

NumPy 的核心是 数组数据结构,它是一个值的网格,构成了 NumPy 功能的核心。通过利用预编译的 C 代码,NumPy 提高了较慢算法的性能,并高效地执行复杂的数学计算。通过支持多维数组和基于数组的操作,NumPy 简化了处理…

介绍 NumPy,第二部分:数组索引

原文:towardsdatascience.com/introducing-numpy-part-2-indexing-arrays-5b381b90d1d0?source=collection_archive---------9-----------------------#2024-09-12

快速成功的数据科学

像专业人士一样切片和切割

Lee VaughanTowards Data Science Lee Vaughan

·发表于 Towards Data Science ·阅读时长 13 分钟·2024 年 9 月 12 日

--

使用 DALL-E3 对数组进行索引

NumPy 是 Python 的基础库,用于数值计算。有了 NumPy,数组(数组本质上是相同数据类型元素的表格)处理了繁重的计算任务。数组经过优化,能够比传统的 Python 数据类型(如列表)执行更快的数学和逻辑运算。

第一部分中,我们讨论了如何创建数组、描述它们并使用点号表示法访问它们的属性。在本文中,我们将探讨如何使用 索引切片 访问数组中的元素,这样你就可以提取元素的值并通过赋值语句进行修改。数组索引使用方括号 [],就像 Python 列表一样。

数组的维度和轴

为了复习第一部分,这里是 1D、2D 和 3D 数组的图形表示,轴已经标注。你需要理解轴的方向才能正确索引。

1D、2D 和 3D 数组的图形表示(来自 Python 科学工具)(这篇以及未来几篇指向我书籍的链接为联盟链接)

索引和切片 1D 数组

介绍 NumPy,第三部分:操作数组

原文:towardsdatascience.com/introducing-numpy-part-3-manipulating-arrays-2685f5d3299d?source=collection_archive---------2-----------------------#2024-09-15

数组的形状、转置、连接与拆分

Lee VaughanTowards Data Science Lee Vaughan

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

--

DALL-E3 想象的数组操作

欢迎来到介绍 NumPy系列的第三部分,这是为那些新接触这个必备 Python 库的人准备的入门教程。第一部分介绍了 NumPy 数组以及如何创建它们。第二部分讲解了数组的索引和切片。第三部分将展示如何通过重塑数组、交换轴、以及合并和拆分数组来操作现有的数组。这些操作对于旋转、放大、平移图像以及拟合机器学习模型等任务非常有用。

形状变化与转置

NumPy 提供了多种方法来改变数组的形状、转置数组(交换列和行)、以及交换轴。你已经在本系列中使用过reshape()方法。

使用reshape()时需要注意的一点是,和所有 NumPy 的赋值操作一样,它创建的是数组的视图而不是副本。在下面的示例中,重塑arr1d数组仅会对数组进行临时更改:

In [1]: import numpy as np

In [2]: arr1d = np.array([1, 2, 3, 4])

In [3]: arr1d.reshape(2, 2)
Out[3]: 
array([[1, 2],
       [3, 4]])

In [4]: arr1d
Out[4]: array([1, 2, 3, 4])

当你想要临时改变数组的形状以便在某些操作中使用时,这种行为非常有用…

介绍 NumPy,第四部分:使用数组进行数学运算

原文:towardsdatascience.com/introducing-numpy-part-4-doing-math-with-arrays-5e77ac595641?source=collection_archive---------13-----------------------#2024-09-16

快速成功数据科学

还有读取和写入数组数据!

Lee VaughanTowards Data Science Lee Vaughan

·发表于数据科学前沿 ·11 分钟阅读·2024 年 9 月 16 日

--

由 DALL-E3 想象的进行数学运算的数组

欢迎来到初学者系列的第四篇也是最后一篇,介绍 NumPy! 在前几篇文章中,我们回顾了 NumPy 的主力阵容——数组:它们是什么以及如何创建它们(第一部分);如何索引和切片它们(第二部分);以及如何操作它们(第三部分)。现在是时候将它们应用到它们的主要目的上:数学运算了。

NumPy 使用两种内部实现来高效地在数组上执行数学运算:向量化广播。向量化支持对相同大小数组的操作,而广播则将这一行为扩展到不同形状的数组。

向量化

ndarrays的一个最强大功能是向量化,它让你可以在数据上执行批量操作,而无需显式的for循环。这意味着你可以一次性对整个数组应用操作,而无需逐个选择其中的元素。

算术运算对于相同大小的数组是逐元素应用的,如下图所示:

由于循环是在后台通过 C 语言实现的代码进行的,向量化可以带来更快的处理速度。让我们来看一个例子,…

介绍 Path-Swarm 和 Super-Swarm:下一代蜂群图

原文:towardsdatascience.com/introducing-path-swarm-super-swarm-next-level-swarm-charts-03605ea87d58?source=collection_archive---------4-----------------------#2024-07-18

了解 Path-Swarm 图表,并使用 Python 创建你自己的图表

Nick GerendTowards Data Science Nick Gerend

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

--

Nick Gerend 的 Path Swarm 和 Super Swarm 图表

引言

本文将重点介绍一种数据可视化技术,它使用圆形排列来表示单个观察值。在我上一篇文章中,我回顾了一种将方形排列应用于数据的方法(我称之为 Quad-Tile 图表),作为一种替代更常见的基于圆形的布局方式,但现在我将回归使用圆形布局,进一步扩展蜂群图。

我们将探讨将基础的蜂群图组件应用于创建新型视觉布局的全新方式,从而将蜂群图提升到另一个灵活性水平!

以下内容将展示两种新的主要蜂群技术,并附带一些有趣的扩展,能够轻松地应用于数据中,用于快速可视化探索数值和分类特征。有关类似数据的更多探索性代码练习,请参阅上一篇文章的引言部分:

介绍 Python 类和数据类

原文:towardsdatascience.com/introducing-python-classes-and-dataclasses-54fbc82ef2cb?source=collection_archive---------0-----------------------#2024-01-23

快速成功数据科学

面向初学者的实践指南

Lee VaughanTowards Data Science Lee Vaughan

·发表于Towards Data Science ·26 分钟阅读·2024 年 1 月 23 日

--

(mche-lee-PC91Jm1DlWA-unsplash)

如果你打算用 Python 进行任何严肃的编程,你需要了解面向对象编程以及数据类的概念。在这篇快速成功数据科学文章中,你将快速、轻松地了解这三者,包括它们的用途、如何使用它们以及为什么你需要它们。

类和 OOP 简介

面向对象编程(OOP)是一种语言模型,它减少了代码重复,使代码更容易更新、维护和重用。因此,现如今大多数商业软件都是使用 OOP 构建的。

与以动作逻辑为基础的过程式编程不同,面向对象编程(OOP)是围绕数据结构构建的,这些数据结构被称为对象,它们由数据和作用于数据的函数(称为方法)组成。对象是由构建的,类就像是对象的蓝图。

类是一个数据类型,当你创建该数据类型的对象时,它也被称为该类的实例。设置实例初始值和行为的过程称为实例化

介绍 Python 的 datetime 模块

原文:towardsdatascience.com/introducing-pythons-datetime-module-13c5895b85a9?source=collection_archive---------4-----------------------#2024-05-14

快速成功的数据科学

初学者处理日期和时间的指南

Lee VaughanTowards Data Science Lee Vaughan

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

--

DALL-E3 制作的 Python 时钟

时间序列是按时间顺序索引的数据点序列。它们是科学和金融数据集中常见的组成部分,在这些数据集中,观察是在一段时间内进行的。

尽管你我都能识别“11/11/1918”作为一个日期,但计算机将其视为一个文本字符串。为了让 Python 能够智能地处理日历日期、小时、分钟、秒等信息,这些字符串必须转换为特殊对象,这些对象能“理解”公历、六十进制(基数 60)时间系统、时区、夏令时、闰年等时间规则。

原生 Python 通过其[datetime](https://docs.python.org/3/library/datetime.html)模块支持时间序列。在这篇快速成功的数据科学文章中,我将介绍如何使用datetime模块的基础知识。在随后的文章中,我会讲解如何利用 Python 的 pandas 数据分析库处理时间序列。

Python 的datetime模块

Python 通过将时间信息视为特定的数据类型,可以正确而高效地处理时间。其内置的datetime模块包括datetime以及组合型datetime类型,允许处理时区、夏令时(DST)、闰年等不同的时间因素。

介绍 Seaborn 对象

原文:towardsdatascience.com/introducing-seaborn-objects-aa40406acf3d?source=collection_archive---------2-----------------------#2024-03-17

快速成功的数据科学

一个绘图之环统治所有!

Lee VaughanTowards Data Science Lee Vaughan

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

--

一个环统治所有的图形(由 Dall-E2 创作)

你是否已经开始使用新的Seaborn 对象系统进行 Python 绘图了?你肯定应该开始使用;它是一个非常棒的工具。

新系统于 2022 年底推出,基于图形语法范式,这一范式也支持Tableau和 R 的ggplot2。这使得它更灵活、更模块化、更直观。使用 Python 绘图从未如此出色。

在这个快速成功的数据科学项目中,你将获得新系统基础的快速入门教程。你还将获得从 Seaborn 对象官方文档汇编的几个实用备忘单。

安装库

我们将在这个项目中使用以下开源库:pandasMatplotlib,和seaborn。你可以在每个超链接中找到安装说明。我推荐你在一个虚拟环境中安装这些库,或者如果你是 Anaconda 用户,在一个专门为这个项目创建的conda 环境中安装。

新的 Seaborn 对象接口

Seaborn 的目标一直是让 Matplotlib——Python 的主要绘图库——更易于使用并且更美观。作为这一目标的一部分,Seaborn 依赖于声明式...

介绍语义标签过滤:通过标签相似性增强检索

原文:towardsdatascience.com/introducing-semantic-tag-filtering-enhancing-retrieval-with-tag-similarity-4f1b2d377a10?source=collection_archive---------2-----------------------#2024-09-09

语义标签过滤

如何利用语义相似度改进标签过滤

Michelangiolo MazzeschiTowards Data Science Michelangiolo Mazzeschi

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

--

要理解这篇文章,需要掌握Jaccard 相似度向量搜索*的知识。此算法的实现已发布在GitHub上,并且完全开源。

多年来,我们已经掌握了如何从不同模态中检索信息,如数字原始文本图像,以及标签

随着定制化用户界面(UI)越来越受欢迎,标签搜索系统成为了一种方便的方式,可以高效且准确地过滤信息。一些常见的标签搜索应用场景包括社交媒体帖子、文章、游戏、电影,甚至是简历的检索。

然而,传统的标签搜索缺乏灵活性。如果我们要筛选包含精确标签的样本,可能会遇到一些情况,尤其是对于只包含几千个样本的数据库,可能没有(或仅有少数)匹配的样本

两种搜索方式在结果稀缺情况下的区别,图片来源:作者

通过以下文章,我试图介绍几种新的算法,据我所知,我一直没有找到相关的资料。我愿意接受批评** 并且 欢迎任何反馈。

传统的标签搜索是如何工作的?

传统系统使用一种叫做Jaccard 相似度的算法(通常通过minhash 算法执行),它能够计算两个元素集合之间的相似度(在我们的例子中,这些元素是标签)。如前所述,这种搜索方法根本不灵活(集合要么包含,要么不包含查询的标签)。

简单与位运算示例(这不是 Jaccard 相似度,但可以让你大致了解过滤方法),图像由作者提供

我们能做得更好吗?

那么,如果我们不仅仅是从匹配标签中过滤样本,而是考虑样本中所有其他不完全相同但与我们选择的标签相似的标签呢?我们可以使算法更加灵活,将结果扩展到非完美匹配的项,但仍然是较好的匹配。我们将直接将语义相似性应用于标签,而不是文本。

引入语义标签搜索

如前所述,这种新方法试图将语义搜索的能力与标签过滤系统相结合。为了构建这个算法,我们只需要一件事:

  • 一个带标签的样本数据库

我将使用的参考数据是开源的Steam 游戏库可从 Kaggle 下载MIT 许可证) — 大约 40,000 个样本,这是测试我们算法的一个不错的样本量。从显示的数据框中可以看到,每个游戏都有多个分配的标签,在我们的数据库中有超过 400 个独特的标签。

示例笔记本中的 Steam 数据框截图,图像由作者提供

现在我们有了起始数据,我们可以继续:算法将在以下步骤中进行阐述:

  1. 提取标签关系

  2. 编码查询和样本

  3. 使用向量检索进行语义标签搜索

  4. 验证

在本文中,我将仅探讨这种新方法背后的数学(有关带有工作演示的代码的详细解释,请参阅以下笔记本:如何使用 simtag 的说明请参见根目录的 README.md 文件)。

1. 提取标签关系

第一个想到的问题是我们如何找到标签之间的关系。请注意,有几种算法可以用于获得相同的结果:

  • 使用统计方法我们可以使用的最简单的方法来提取标签关系叫做共现矩阵,这是我在本文中将采用的格式(因其高效性和简单性)。

  • 使用深度学习:最先进的深度学习方法都基于嵌入式神经网络(例如,过去使用Word2Vec,现在常用的则是变换器模型,如 LLMs),这些方法能够提取样本之间的语义关系。创建一个神经网络来提取标签关系(以自编码器的形式)是一个可能的选项,并且在面对特定情境时,通常建议这样做。

  • 使用预训练模型:由于标签是使用人类语言定义的,因此可以使用现有的预训练模型来计算已经存在的相似度。这通常会更快,也不会那么麻烦。然而,每个数据集都有其独特性。使用预训练模型将忽略客户行为

    例如,我们稍后会看到 2D 与 Fantasy 之间有强关联:这种配对使用预训练模型永远无法发现。

算法的选择可能取决于许多因素,特别是当我们需要处理庞大的数据池或存在可扩展性问题时(例如,# 标签将等于我们的向量长度:如果标签太多,我们需要使用机器学习来解决这个问题)。

a. 使用米开朗基罗相似度构建共现矩阵

如前所述,我将使用共现矩阵来提取这些关系。我的目标是找到每一对标签之间的关系,我将通过对整个样本集合(S)应用以下计数来做到这一点,使用 IoU(交集除以并集):

计算标签对之间相似度的公式,作者提供的图像

这个算法与 Jaccard 相似度非常相似。虽然它作用于样本,但我介绍的这个算法作用于元素,然而(据我所知)这个特定的应用尚未被正式化,所以我们可以将其称为米开朗基罗相似度。(公平地说,这个算法的使用曾在StackOverflow 问题中提到过,但从未被正式化)。

Jaccard 相似度米开朗基罗相似度的区别,作者提供的图像

对于 40,000 个样本,提取相似度矩阵大约需要一个小时,结果将是:

我们样本列表中所有独特标签的共现矩阵 S,作者提供的图像

让我们手动检查一些非常常见标签的前 10 个样本,看看结果是否合理:

共现矩阵中提取的样本关系,作者提供的图像

结果看起来非常有前景!我们从纯粹的分类数据(仅能转换为 0 和 1)开始,但我们已经提取了标签之间的语义关系(甚至没有使用神经网络)。

b. 使用预训练神经网络

同样,我们可以使用预训练编码器提取我们样本之间的现有关系。然而,这种解决方案忽略了只能从我们的数据中提取的关系,仅关注人类语言的现有语义关系。这可能不是在零售数据上工作的最佳解决方案。

另一方面,通过使用神经网络,我们不需要构建关系矩阵:因此,这是一个适合可扩展性的解决方案。例如,如果我们必须分析大量的 Twitter 数据,我们将达到 53,300 个标签。从这个标签数计算共现矩阵将得到一个大小为2,500,000,000的稀疏矩阵(这相当不切实际)。相反,通过使用一个标准的编码器,输出长度为 384 的向量,结果矩阵的总大小将是19,200,200

使用预训练编码器编码标签集的快照

2. 编码查询和样本

我们的目标是构建一个能够支持语义标签搜索的搜索引擎:在我们一直在构建的格式下,唯一能够支持此类企业的技术是向量搜索。因此,我们需要找到一个合适的编码算法,将我们的样本和查询转换为向量。

在大多数编码算法中,我们使用相同的算法对查询和样本进行编码。然而,每个样本包含多个标签,每个标签由一组不同的关系表示,我们需要在单个向量中捕捉这些关系

协变量编码, 图片由作者提供

此外,我们需要解决前面提到的可扩展性问题,我们将通过使用PCA 模块来解决(如果我们使用共现矩阵,则可以跳过 PCA,因为不需要压缩我们的向量)。

当标签数量变得过大时,我们需要放弃计算共现矩阵的可能性,因为其计算量是平方级别的。因此,我们可以使用预训练的神经网络提取每个现有标签的向量(PCA 模块的第一步)。例如,all-MiniLM-L6-v2将每个标签转换为长度为 384 的向量。

我们可以转置获得的矩阵,并对其进行压缩:我们最初使用 1 和 0 编码我们的查询/样本,代表可用的标签索引,结果是一个与我们初始矩阵(53,300)长度相同的初始向量。此时,我们可以使用我们预计算的 PCA 实例来压缩相同的稀疏向量至 384 维

编码样本

在我们的样本中,过程在 PCA 压缩后(激活时)刚好结束。

编码查询:协变量编码

然而,我们的查询需要不同的编码方式:我们需要考虑每个现有标签相关的关系。这个过程首先通过将我们的压缩向量加到压缩矩阵上(所有现有关系的总和)来执行。现在我们得到了一个矩阵(384x384),我们需要对其进行平均,从而得到我们的查询向量。

因为我们将使用欧几里得搜索,它会首先优先搜索得分最高的特征(理想情况下是我们用数字 1 激活的那个),但它也会考虑其他较小的得分。

加权搜索

因为我们是在对向量进行平均计算,我们甚至可以为这个计算应用一个权重,向量将会根据查询标签的不同而受到不同的影响。

3. 使用向量检索执行语义标签搜索

你可能会问:为什么我们要经历这个复杂的编码过程,而不是直接将标签对输入到一个函数中并获得一个得分——f(query, sample)

如果你熟悉基于向量的搜索引擎,你已经知道答案了。通过对每一对进行计算,在仅仅 40,000 个样本的情况下,所需的计算力是巨大的(单次查询可能需要10 秒):这不是一种可扩展的做法。然而,如果我们选择对 40,000 个样本进行向量检索,搜索将在0.1 秒内完成:这是一个高度可扩展的做法,在我们的案例中非常完美。

4. 验证

为了让一个算法有效,必须进行验证。目前,我们缺乏适当的数学验证(乍一看,从 M 中平均相似度得分已经显示出非常有前景的结果,但仍需要进一步研究,以便有一个客观的度量并且有证据支持)。

然而,当使用对比示例进行可视化时,现有的结果是相当直观的。以下是两种搜索方法的顶部搜索结果(您看到的是分配给此游戏的标签)

传统标签搜索语义标签搜索的对比,图片由作者提供

  • 传统标签搜索我们可以看到,传统搜索可能(没有额外规则时,样本是基于所有标签的可用性过滤的,而不是排序的)返回一个拥有更多标签的样本,但其中许多标签可能并不相关。

  • 语义标签搜索

    语义标签搜索根据所有标签的相关性对所有样本进行排序,简而言之,它排除包含无关标签的样本

这个新系统的真正优势在于,当传统搜索未能返回足够的样本时,我们可以使用语义标签搜索选择任意数量的样本

在结果稀缺时,两种搜索的区别,图片由作者提供

在上面的例子中,使用传统的标签过滤无法从 Steam 库中返回任何游戏。然而,通过使用语义标签过滤,我们仍然能获得结果,虽然这些结果并不完美,但是最符合我们查询的那些。你所看到的这些是与我们搜索最匹配的前五款游戏的标签。

结论

在此之前,无法在不依赖复杂方法的情况下过滤标签,同时考虑到它们的语义关系,例如聚类、深度学习或多个 knn 搜索

该算法所提供的灵活性应该使其脱离传统的人工标签方法,这些方法迫使用户在预定义的标签集之间做出选择,同时也开辟了使用 LLM 或 VLM 的可能性,可以自由地为文本或图像分配标签,而不受限于预先存在的结构,从而为可扩展且改进的搜索方法提供了新的选项。

我怀着最好的祝愿将这个算法开放给全世界,并希望它能够被充分利用。

介绍全新的 Anthropic PDF 处理 API

原文:towardsdatascience.com/introducing-the-new-anthropic-pdf-processing-api-0010657f595f?source=collection_archive---------0-----------------------#2024-11-27

图片由 AI(Dalle-3)生成

Anthropic Claude 3.5 现在支持理解 PDF 输入

托马斯·里德Towards Data Science 托马斯·里德

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

--

在过去的几周里,Anthropic 发布了一些令人兴奋的 beta 功能,这些功能大多未受到关注。其中之一是它的新 token 计数 API。我已经写了一篇关于这个的文章,你可以通过点击下面的链接阅读。

## 介绍全新的 Anthropic Token 计数 API

使用 Claude 时,更加密切地关注成本

towardsdatascience.com

另一个令人兴奋的功能,也是本文的主题,是 Claude 3.5 现在可以处理 PDF,并理解 PDF 文档中的文本和视觉内容。

PDF 功能

Claude 支持任何标准 PDF 文件,允许你查询文档中的文本、图片、图表和表格。以下是一些常见的使用案例:

  • 分析财务报告,解读图表和表格

  • 从法律文档中提取关键信息

  • 协助文档翻译

  • 将文档内容转换为结构化格式

限制

推出新的 Anthropic 令牌计数 API

原文:towardsdatascience.com/introducing-the-new-anthropic-token-counting-api-5afd58bad5ff?source=collection_archive---------6-----------------------#2024-11-08

图片来自作者

使用 Claude 时,请更密切关注你的成本

Thomas ReidTowards Data Science Thomas Reid

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

--

Anthropic 最近几天发布了一些令人兴奋的 Beta 功能,这些功能在很大程度上未被注意到。其中一项功能是能够使用其模型处理 PDF,现在这些模型能够理解 PDF 文档中的文本和视觉内容。我可能会在稍后的时间写一篇关于这方面的文章。

另一个令人兴奋的 Beta 功能,也是本文的主题,是引入了令牌计数功能。关键是,在将内容发送给 Claude 之前,你可以计算用户消息、PDF 和图像中的令牌数量。对于那些喜欢密切监控令牌使用成本的人来说,这是一个好消息。

根据 Anthropic 的官方公告(链接在这里),

“令牌计数端点接受与创建消息相同结构的输入列表,包括支持系统提示、工具、图像和 PDF。响应包含输入令牌的总数。”

并支持以下模型,

“Claude 3.5 叙事诗

Claude 3.5 俳句

Claude 3 俳句

Claude 3 大作”

介绍四格图与方形图:将数据方形化

原文:towardsdatascience.com/introducing-the-quad-tile-chart-squaremap-squarify-your-data-20be336a1dd7?source=collection_archive---------4-----------------------#2024-02-03

了解四格图并使用 Python 创建自己的图表

Nick GerendTowards Data Science Nick Gerend

·发布于 Towards Data Science ·阅读时间 34 分钟 ·2024 年 2 月 3 日

--

四格图和方形图由 Nick Gerend 创作

介绍

在数据可视化中,可用画布的大小和形状始终是一个关键因素。这是我在另一篇最近的文章中提到的话题,我在文章中介绍了一种名为 水晶条形图 的图表,采用两轴方法来节省空间。

## 介绍水晶条形图:可视化顺序差异聚类

了解水晶条形图并使用 Python 创建自己的图表

towardsdatascience.com

在本文中,我将探讨我开发的一种无轴方法,将一组值可视化为方形,并通过将容器填充为凸多边形或简单凹多边形的形式来节省空间。我还尽力将代码填充到本文中,希望它能够作为一个有趣的教程,帮助你了解其他一些可能有用的技巧和可视化方法。

本文的目标是考虑标准和非传统的容器形状(如下所示),并以优化空间、提供直观大小的方式,将一组值呈现为这些形状中的相似形状。

在 pandas 中介绍时间序列

原文:towardsdatascience.com/introducing-time-series-in-pandas-09c7cab569b4?source=collection_archive---------5-----------------------#2024-05-20

快速成功的数据科学

处理日期和时间的初学者指南

Lee VaughanTowards Data Science Lee Vaughan

·发表于Towards Data Science ·阅读时间:25 分钟·2024 年 5 月 20 日

--

Panda 时间,来自 DALL-E3

参考时间索引的数据称为时间序列。可以想象一下股票价格、温度记录和 Google 趋势。虽然我不认识喜欢处理时间序列的人,但 Python 尽力让这一过程尽可能简单。

Python 及其 pandas 数据分析库将日期和时间视为特殊对象,这些对象“了解”公历、六十进制(60 进制)时间系统、时区、夏令时、闰年等的机制。Python 本身通过其[datetime](https://docs.python.org/3/library/datetime.html)模块支持时间序列,而 pandas 则专注于使用日期数组,例如作为 DataFrame 中的索引或列。

在这篇快速成功的数据科学文章中,我们将回顾 pandas 如何处理日期和时间。目标是向你介绍如何在 pandas 中处理时间序列的基础知识,并使你能够熟练掌握这一主题。如需更详细的内容,请访问 pandas 的用户指南。如果你需要安装 pandas,请参考入门指南

pandas 中的时间概念

正如你可能预料的,pandas 具有处理时间序列的广泛能力。这些功能基于 NumPy 的datetime64……

介绍 tmap 用于可视化和数据分析

原文:towardsdatascience.com/introducing-tmap-for-visualization-and-data-analysis-82c51cd17632?source=collection_archive---------7-----------------------#2024-02-24

对 R 中用于地理空间数据探索的 tmap 库的简要介绍。

Gustavo R SantosTowards Data Science Gustavo R Santos

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

--

图片来自Andrew Stutesman,来源:Unsplash

介绍

不是每个数据科学家都需要在 Python 和 R 之间做出选择。我经常看到围绕这个话题的讨论,其中一些让人烦恼,另一些则相当有趣。但事实是,会有很多数据科学家能够使用这两种语言,因为他们不需要部署任何东西或将整个应用程序上线。他们只需要分析一些数据,并创建一个简单的执行报告,当然也可以用传统的 PowerPoint 来展示。

最近,我需要做这件事。我必须获取一些包含纬度经度信息的数据点,分析这些点,并做出一个好的数据故事,详细展示我的洞察。

我得说,我喜欢用 R 工作。我认为这个语言容易编程,而且包也做得非常好。我的宝藏库之一就是tmap库。

我的宝藏库之一是**tmap****。

Thematic Map,简称tmap,是一个在 R 语言中构建的库,用于处理空间数据,名称中的thematic意味着我们可以通过自定义和可视化数据来进行…

介绍单变量样本推荐系统:如何在一个向量中描述客户行为

原文:towardsdatascience.com/introducing-univariate-exemplar-recommenders-how-to-profile-customer-behavior-in-a-single-vector-c90c9943fe7d?source=collection_archive---------3-----------------------#2024-12-04

客户画像

调查并改进当前的客户画像方法

Michelangiolo MazzeschiTowards Data Science Michelangiolo Mazzeschi

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

--

要理解本文,需具备嵌入、聚类和推荐系统的相关知识。此算法的实现已发布在GitHub并完全开源。我欢迎批评,并欢迎任何反馈*。

如今,大多数平台都意识到,为每个客户量身定制个人化选择会提高用户参与度。正因如此,推荐系统领域一直在不断发展,每年都会诞生新的算法。

层次聚类,图像来源:作者

不幸的是,目前没有现有的分类体系在跟踪该领域的所有算法。虽然大多数推荐算法,如矩阵分解,使用神经网络基于一系列选择进行推荐,但在本文中,我将专注于那些采用基于向量的架构来跟踪用户偏好的算法

样本推荐系统

由于嵌入的简单性,每个可以推荐的样本(例如产品、内容等)都通过一个预训练的神经网络(例如矩阵分解)转化为一个向量:然后我们可以使用 knn 算法推荐相似的产品/客户。遵循这种范式的算法被称为基于向量的推荐系统。然而,当这些模型考虑到用户的先前选择时,它们为基础架构增加了一个顺序层,并在技术上被称为基于向量的 顺序推荐系统。由于这些架构越来越难以记忆(和发音),我将它们称为样本推荐系统:它们从初始选择集中提取一组代表性向量来表示用户向量

推荐系统的细分,图片来源:作者

基于该架构构建的第一个系统之一是Pinterest,它运行在其 Pinnersage 推荐引擎之上:这个可扩展的引擎能够管理超过 20 亿个钉图,运行其特定架构并对每个用户的选择进行聚类。正如我们所想,这在扩展时代表了一个计算挑战。尤其是在发现协变量编码后,我想介绍四种互补的架构(其中两种,特别是文章中提到的名称),它们可以减轻聚类算法的压力,在尝试分析每个客户时。你可以参考以下图示来区分它们。

样本推荐系统概述,图片来源:作者

请注意,以上所有方法都属于基于内容的过滤,而非协同过滤。关于样本架构,我们可以识别出两个主要定义参数堆栈内聚类实现(我们可以在样本嵌入或用户嵌入上执行聚类),以及用于存储用户偏好的向量数量

堆栈内聚类实现

再以 Pinnersage 为例,我们可以看到它是如何为每个用户执行一个新颖的聚类迭代。尽管从准确度的角度来看,这具有优势,但从计算角度而言,非常耗费资源。

后聚类

当聚类应用于用户嵌入时,我们可以将这种方法(在此特定堆栈中)称为后聚类。尽管这种方法看起来效率不高,但在数十亿个样本上应用非参数聚类算法几乎是不可能的,可能也不是最好的选择。

预聚类

在某些使用案例中,在样本数据上应用聚类可能是有利的:我们可以将这种方法(在这个特定的堆栈中)称为预聚类。例如,零售商店可能需要跟踪数百万用户的历史记录,这将需要与 Pinnersage 架构相同的计算资源。

然而,与 Pinterest 平台相比,零售商店的样本数量不应超过 10,000,而后者则是惊人的20 亿。在如此少量的样本下,对样本嵌入进行聚类非常高效,如果合理利用,这也将减轻对用户嵌入进行聚类的需求。

引入单变量架构

如前所述,创建这些架构时面临的最大挑战是可扩展性。每个用户有数百个过去的选择记录,需要为示例提取进行计算。

多变量架构

构建基于向量的推荐系统的最常见方式是将每个用户选择与现有的预计算向量绑定。然而,即使我们采用衰减函数来最小化在计算中需要考虑的向量数量,我们仍然需要在计算时填充所有的向量缓存。此外,在检索时,向量不能存储在执行计算的机器上,而需要从数据库中查询:这为可扩展性带来了额外的挑战。

这种方法的流程是推荐中的方差有限。推荐的样本将在空间上非常接近(样本方差最小化),并且仅属于同一类别(除非有更复杂的逻辑定义这种交互)。

多变量示例推荐,图片由作者提供

何时使用:这种方法(我这里只考虑模型的行为,而非其计算需求)适用于那些我们可以推荐一批来自同一类别的样本的应用。艺术或社交媒体应用就是一个例子。

单变量架构

使用这种新方法,我们可以通过一个单一的向量来存储每个用户的选择,并随着时间推移不断更新。这应该是可扩展性的显著提升,最小化来自knn检索的计算压力。

更复杂的是,我们有两个索引可以进行聚类。我们可以聚类项目类别(两者都使用标签标注)。没有绝对优越的方法,我们必须根据使用案例选择其中一个。

> 基于类别

本文完全基于构建基于类别的模型。在标记数据后,我们可以执行聚类,将数据分组为类别层次结构(如果我们的数据已经按类别组织,则无需应用层次聚类)。

这种方法的主要优点是,指示用户偏好的示例将与相似类别相关联(增加了产品的多样性)。

单变量基于类别的示例推荐,图片由作者提供

何时使用:有时,我们希望向客户推荐整个类别,而不是单个产品。例如,如果我们的用户喜欢购买衬衫(而且碰巧示例位于红色衬衫的潜在区域),我们更有利于推荐他整个服装类别,而不是仅仅红色衬衫。这种方法最适合零售和时尚公司。

> 基于项目

使用基于项目的方法,我们是在对样本进行聚类。这将允许我们捕捉数据的更细粒度信息,而不是仅仅关注分开的类别:我们希望超越产品分类的限制,并跨现有类别推荐项目。

单变量基于项目的示例推荐,图片由作者提供

何时使用:能够充分利用这种方法的最佳公司是人力资源公司和拥有跨类别产品的零售商(例如:视频游戏)。

单变量示例推荐系统

最后,我们可以深入解释基于类别方法背后的架构。该算法将通过仅存储一个单一的向量来执行示例提取:唯一能够管理它的技术是协变量编码,因此我们将使用标签来处理数据。由于它使用预聚类,它非常适合样本数量可管理但用户数量无限制的应用场景。

在这个例子中,我将使用开源的Steam 游戏库集合(可从 Kaggle 下载MIT 许可证),这是一个完美的适用于大规模推荐系统的案例:Steam 最多使用 450 个标签,并且该数量可能会随着时间的推移有所增加;然而,它是可管理的。这些标签可以很容易地进行聚类,如果我们对聚类分配有疑问,甚至可以进行手动干预。最后,它服务于数百万用户,证明它是一个现实的应用案例

Steam 游戏数据集的示例,图片由作者提供

它的架构可以分为以下几个阶段:

注意,当创建此架构的样本代码时,我使用 LLM 使整个过程摆脱任何人为监督。然而,LLM 仍然是可选的*,虽然它们可能会提高推荐系统的水平,但它们不是这个系统的核心部分。

  1. 样本标注

    我们需要确保为每个样本分配标签。由于语义标签过滤,我们不需要依赖零样本,而是可以让 LLM 在没有任何监督的情况下管理此过程。

  2. 预聚类

    我们将把标签嵌入分成不同的聚类。为了提高准确性,我们将使用层次聚类,深度为 3。

  3. 聚类标注

    一旦我们定义了聚类树,就需要为每个生成的超级聚类打上标签。我们仍然可以使用 LLM 来实现这一目的。如果你决定避免使用 LLM,需要注意的是,聚类可以保持为数字形式(这可能只是改变了推荐系统用户的感知)。

  4. 平衡非均匀标签频率

    从标签列表中挑选标签的第一个挑战是,出现频率最高的标签(并且被分配到一个聚类中)会严重偏向推荐系统建议该聚类。我们需要确保每个聚类有相同的推荐概率。我们可以通过添加一个自定义的倍增器来实现这一点,从而统一每个聚类被推荐的概率。

  5. 单变量序列编码

    现在我们已经定义了编码权重,可以将用户历史编码为一个向量,并且可以随着时间的推移更新它(使用衰减函数来去除过时的用户偏好)。

  6. 考虑可扩展性:剪枝机制

    因为我们的向量维度等于标签的数量,所以我们需要找到一种方法来限制向量的大小。PCA 是一个有效的选择,但由于对向量的求和操作,特征剪枝已被证明更为高效。

  7. 示例估算

    创新之处在于这里。我们可以将用户档案编码为单一示例并且仍然获得单独的聚类推荐,而不会有因为将多个示例平均化而导致的信息丢失。这意味着之前的所有多变量方法都与此架构不兼容。

让我们从对单变量示例推荐系统的全面解释开始:

1. 样本标注

在我们的参考数据集中,所有样本已经使用标签进行了标注。如果我们恰好正在处理带标签的数据,可以轻松使用 LLM 来完成这项工作,通过提示请求为每个样本列出标签。正如我在关于语义标签过滤的文章中所解释的那样,我们不需要使用零样本来指导标签选择,整个过程可以完全无监督。

我们的样本数据截图,每个样本都标注了标签,图像由作者提供

2. 预聚类

如前所述,该推荐系统背后的想法是首先将数据组织成簇,然后识别出最常见的簇(示例簇),这些簇定义了每个用户的偏好。由于数据理想情况下非常小(数千个标签对比数十亿个样本),因此聚类不再是一种负担,可以在标签嵌入上进行,而不是在数百万个用户嵌入上

标签数量越多,使用层次结构来管理其复杂性就越有意义。理想情况下,我不仅希望跟踪每个用户的主要兴趣,还希望跟踪他们的子兴趣并据此进行推荐。通过使用树状图,我们可以通过使用阈值水平来定义不同层次的簇。

第一个超级簇(第一层)将是使用阈值 11.4 的结果,生成前 81 个簇。我们还可以看到它们的分布是不均匀的(一些簇比其他簇大),但总体来看,并不会过于偏斜。

层次聚类,第一层,阈值=11.4,图像来源:作者

第一层聚类的所有簇大小,图像来源:作者

下一个聚类层级将由一个较小的阈值(9)定义,它将数据组织成 181 个簇。同样,第一层聚类的大小分布不均匀,但只有两个大簇,因此这不应是一个大问题。

层次聚类,第二层,阈值=9,图像来源:作者

第二层聚类的所有簇大小,图像来源:作者

这些阈值是随意选择的。尽管有非参数聚类算法可以在没有任何人工输入的情况下执行聚类过程,但它们管理起来相当具有挑战性,尤其是在大规模数据上,并且会出现一些副作用,比如簇大小的分布不均匀。如果我们的簇中有一些过大(例如,某个簇可能占据总体数据的 20%),那么它们可能会包含大部分推荐内容,但并没有太多意义。

我们在执行聚类时的优先事项是在最大化簇的数量的同时获得最均匀的分布,以便尽可能多地划分数据并进行不同的表示。

3. 聚类标记

因为我们选择在现有数据的基础上对两个层次的深度进行聚类,所以我们达到了总共三层。最后一层由单个标签构成,并且是唯一的标记层。其他两层则仅包含簇编号,没有适当的命名。

为了解决这个问题(请注意,这一步骤中的超级簇标记并非强制执行,但可以改善用户与我们推荐系统的交互方式),我们可以在超级簇之上使用大型语言模型(LLM)。

让我们尝试通过将每个组中的标签喂入来自动标记所有簇:

不同深度的簇标签,图片来自作者

现在,我们的簇也已经正确标记完毕,可以开始构建我们的序列推荐系统的基础了。

4. 平衡非均匀标签频率

到目前为止,我们已经完成了简单的部分。现在我们已经准备好了所有元素来创建推荐系统,但我们仍然需要调整不平衡。虽然展示这一步骤在推荐系统完成之后会更加直观,但不幸的是,它是推荐系统基础结构的一部分,你需要和我一起承担这个部分。

4.1 如果我们跳过平衡呢?

让我们稍微提前一点,展示完成的推荐系统的能力,简单地跳过这个必要的步骤。通过给每个标签分配 1 的得分,有些标签会非常常见,从而严重扭曲推荐得分。

以下是一个蒙特卡洛模拟来自数据集的 5000 次随机标签选择。我们所看到的是在加总得分后被随机选择的簇的分布。正如我们所见,分布高度倾斜,肯定会使推荐系统倾向于得分最高的簇。

在 10k 次模拟中推荐的簇频率,图片来自作者

例如,簇“黑暗北欧王国”包含标签独立游戏,它出现在 64%的所有样本中(基本上几乎不可能不反复选择)。

推荐簇的示例,图片来自作者

为了更精确一些,让我们直接模拟 100 次不同的随机会话,每次选择会话中的前 3 个簇(我们跟踪的主要用户偏好),让我们模拟整个用户会话,以便数据更完整。尤其是在使用衰减函数时,分布不均匀且随时间不断变化是正常的。

在 10k 次模拟中推荐的簇频率,图片来自作者

然而,如果倾斜过度,结果就是大多数用户95%的时间将会推荐前 5%的簇(这不是精确的数字,只是为了证明我的观点)。

4.2 平衡概率分布

相反,让我们使用一个适当的频率调整公式。由于每个簇的概率不同,我们希望分配一个得分,在用它来平衡我们用户向量的权重时,能够平衡簇的检索:

用于平衡概率非均匀性的得分函数,图片来自作者

让我们看一下为4 个不同的随机簇分配的得分:

推荐聚类的示例,作者提供的图片

如果我们将评分应用于随机选择(5000 次选择,计数频率并调整上述权重),我们可以看到标签分布现在已经平衡(轮廓~“肾上腺素冲击”是由重复名称引起的):

10k 次模拟中的聚类概率,作者提供的图片

事实上,通过查看波动的正态分布,我们可以看到,选择任何一个聚类的标准差大约是 0.1,这非常低(尤其是与之前相比)。

10k 次模拟中的波动分布,作者提供的图片

通过复制 100 次会话,我们可以看到,即使是伪均匀的概率分布,聚类也会随着时间的推移,遵循帕累托原则积累。

10k 次模拟中的推荐聚类频率,作者提供的图片

5. 单变量顺序编码

现在是构建顺序机制的时候,以跟踪用户的选择随时间的变化。我理想化的机制基于两个独立的向量(经过处理后最终合并成一个,因此是单变量的),分别是历史向量缓存向量

历史向量用于在现有聚类上执行 k 近邻算法。一旦会话结束,我们将使用新的用户选择更新历史向量。同时,我们会通过一个衰减函数调整现有的值,使得现有的权重随着时间的推移逐渐减少。通过这样做,我们确保跟上客户的趋势,并给新选择赋予更多权重,而不是旧的选择

与其在每次用户做出选择时更新向量(这在计算上效率较低,此外,我们还可能会让较旧的选择过早衰减,因为每次用户交互都会触发衰减机制),我们可以存储一个临时向量,该向量仅对当前会话有效。每次用户交互转化为向量并使用标签频率作为独热权重,将被加到现有的缓存向量中。

向量和流工作流程,作者提供的图片

一旦会话结束,我们将从数据库中检索历史向量,将其与缓存向量合并,并应用调整机制,例如衰减函数和修剪机制,正如我们稍后所见)。历史向量更新后,将替换旧的向量并存储在数据库中。

会话推荐器工作流程,作者提供的图片

采用这种方法的两个原因是:最小化旧的和新的交互之间的权重差异,以及使整个过程具有可扩展性和计算效率。

6. 修剪机制

系统已经完成。然而,存在一个额外的问题:协变量编码有一个缺陷:其基础向量是根据编码标签的数量按比例缩放的。例如,如果我们的数据库达到 10 万标签,向量将具有等同数量的维度。

原始的协变量编码架构已经考虑到这个问题,提出了一种 PCA 压缩机制作为解决方案。然而,当应用到我们的推荐系统时,PCA 在迭代求和向量时会导致信息丢失。因为每次用户选择都会将现有向量与新的向量相加,因此这种解决方案不建议使用。

然而,如果我们无法压缩向量,我们可以修剪掉得分最低的维度。系统将基于向量中最相关的得分执行 kNN;这种直接的特征工程方法不会对最终推荐结果产生负面影响(更好的是,不会过度影响)。

修剪机制,图像由作者提供

通过修剪我们的向量,我们可以任意设置向量的最大维度数。在不改变标签索引的情况下,我们可以开始操作稀疏向量,而不是密集向量,稀疏向量是一种只保存我们向量中活动索引的数据结构,能够实现无限扩展。我们可以将从完整向量(密集向量)获得的推荐与稀疏向量(修剪向量)进行比较。

使用密集向量与稀疏向量进行相同用户向量推荐,图像由作者提供

如我们所见,我们可以察觉到一些细微的差别,但向量的整体完整性保持了下来,以换取可扩展性。这种过程的一个非常直观的替代方法是,在标签级别进行聚类,保持向量大小固定。在这种情况下,标签将需要分配给语义上最接近的标签,并且不会占用其专有的维度。

7. 示例估计

现在你已经完全掌握了这种新方法背后的理论,我们可以更清楚地进行比较。在多变量方法中,第一步是使用聚类识别出用户的主要偏好。如我们所见,这个过程需要我们存储尽可能多的向量,以作为示例。

示例提取,图像由作者提供

然而,在单变量方法中,因为协变量编码作用于编码数据的转置版本,我们可以使用我们历史向量的部分来存储用户偏好,从而只使用一个向量完成整个过程。使用历史向量作为查询在编码标签中进行搜索:其来自 kNN 搜索的 top-k 结果将等同于 top-k 偏好聚类。

多变量与单变量向量集的区别,图像由作者提供

8. 推荐方法

既然我们已经捕捉到多个偏好,我们如何规划推荐项目?这是两个系统之间的主要区别。传统的多变量推荐系统将使用示例来推荐 k 个项目给用户。然而,我们的系统已经为客户分配了一个超级簇和其下的顶级子簇(根据我们的标签分割级别,我们可以增加层级数)。我们不会推荐前 k 个项目,而是推荐前 k 个子簇

使用 groupby 代替向量搜索

到目前为止,我们一直使用向量来存储数据,但这并不意味着我们需要依赖向量搜索来执行推荐,因为它比 SQL 操作要慢得多。请注意,确实可以使用向量搜索在用户数组上获得完全相同的结果。

如果你在疑惑为什么要从基于向量的系统切换到基于计数的系统,这是一个合理的问题。简单的回答是这是多变量系统最忠实的复制品(如参考图像所示),但它具有更高的可扩展性(使用 pandas 在 16 个 CPU 核心上可以达到每秒 3000 个推荐)。最初,单变量推荐系统设计是使用向量搜索的,但正如所展示的,存在更简单、更好的搜索算法。

模拟

让我们进行一个完整的测试,我们可以监控它。我们可以使用示例笔记本中的代码:对于我们的简单示例,用户至少选择一个带有相应标签的游戏。

# if no vector exists, the first choices are the historical vector
historical_vector = user_choices(5, tag_lists=[['Shooter', 'Fantasy']], tag_frequency=tag_frequency, display_tags=False)

# day1
cached_vector = user_choices(3, tag_lists=[['Puzzle-Platformer'], ['Dark Fantasy'], ['Fantasy']], tag_frequency=tag_frequency, display_tags=False)
historical_vector = update_vector(historical_vector, cached_vector, 1, 0.8)

# day2
cached_vector = user_choices(3, tag_lists=[['Puzzle'], ['Puzzle-Platformer']], tag_frequency=tag_frequency, display_tags=False)
historical_vector = update_vector(historical_vector, cached_vector, 1, 0.8)

# day3
cached_vector = user_choices(3, tag_lists=[['Adventure'], ['2D', 'Turn-Based']], tag_frequency=tag_frequency, display_tags=False)
historical_vector = update_vector(historical_vector, cached_vector, 1, 0.8)

compute_recommendation(historical_vector, label_1_max=3)

在三次会话结束时,这些是从我们的推荐系统提取的前三个示例(label_1)

三次会话后的推荐,图像由作者提供

在这个笔记本中,你将找到执行蒙特卡洛模拟的选项,但验证它们的方式并不简单(主要是因为团队游戏没有被标记为最高准确度,而且我注意到大多数小型游戏列出了太多不相关或常见的标签)。

结论

最受欢迎的推荐系统架构仍然没有考虑会话历史,但随着新算法的发展和计算能力的提高,现在可以应对更高层次的复杂性。

这种新方法应该为市场上可用的顺序推荐系统提供一种全面的替代方案,但我相信总有改进的空间。为了进一步增强这一架构,可以考虑从基于聚类的方法切换到基于网络的方法。

需要注意的是,这个推荐系统只有在应用于有限数量的领域时才能表现出色,但在计算资源匮乏或需求极高的情况下,仍具有闪光的潜力。

介绍 zeroCPR:寻找互补产品的一种方法

原文:towardsdatascience.com/introducing-zerocpr-an-approach-to-finding-complementary-products-20f2b98c5d03?source=collection_archive---------8-----------------------#2024-07-15

推荐系统

当前的机器学习模型可以推荐相似产品,但互补产品呢?

Michelangiolo MazzeschiTowards Data Science Michelangiolo Mazzeschi

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

--

完整的 zeroCPR 代码是开源的,可以在我的 Github 仓库 上找到。

AI 推荐系统领域,机器学习模型已广泛应用于推荐相似样本,无论是产品、内容,甚至是推荐相似的联系人。这些预训练模型大多数是开源的,可以直接使用,而无需从头开始训练模型。然而,由于缺乏大数据,我们没有任何可以依赖的开源技术来推荐互补产品。

在以下文章中,我提出了一个框架(以用户友好型库的形式提供代码),利用大语言模型(LLM)以一种不昂贵的方式发现互补产品。

我介绍这个框架的目标是:

  • 可扩展

    这是一个框架,运行时不需要监督,不容易出错,输出应易于结构化,以便与其他工具结合使用。

  • 经济实惠

    它应该足够经济实惠,可以以最低花费(大约 1 美元每 1000 个计算产品——使用 Groq 定价)找到成千上万产品的互补品,此外,不需要任何微调(这意味着它甚至可以在单一产品上进行测试)。

***如需支持或功能请求,请随时与我联系。在本文中,我介绍了框架(及其相应的库) zeroCPR 以及一种新的提示技术,我称之为 Chain-of-DataFrame ,用于列表推理。

理解问题

在深入研究 zeroCPR 框架的理论之前,让我们先理解一下为什么当前的技术在这一领域受到限制:

为什么神经网络在推荐相似产品方面表现突出?

这些模型在这一任务中表现出色,因为神经网络天生会将具有相似特征的样本分组到同一个空间区域。简而言之,举例来说,如果一个神经网络在人的语言上进行训练,它会将具有相似意义的单词或句子分配到同一个空间区域。按照同样的原理,如果在客户行为上进行训练,具有相似行为的客户将被安排到相似的空间区域。

嵌入可视化,图片来源:projector.tensorflow.org/

能够推荐相似句子的模型被称为语义模型,它们既轻便又易于使用,可以创建基于语言相似性而非客户行为的推荐系统。

缺乏客户数据的零售公司可以通过利用语义模型的能力轻松推荐相似产品。

那么,互补产品呢?

然而,推荐互补产品是一个完全不同的任务。据我所知,没有开源模型能够完成这样的任务。零售公司基于自己的数据训练定制的互补推荐系统,导致这些模型难以泛化,并且是行业特定的。

zeroCPR 框架

zeroCPR代表零-shot 互补产品推荐系统。其工作原理很简单。通过接收可用产品列表参考产品,它试图找出在你的列表中是否有可以推荐的互补产品。

大型语言模型(LLM)可以轻松推荐互补产品。你可以要求 ChatGPT 输出哪些产品可以与牙刷搭配,它很可能会推荐牙线牙膏

然而,我的目标是创建一个企业级工具,能够与我们的自定义数据协作。ChatGPT 可能是正确的,但它生成的是一个无法与我们的产品列表集成的非结构化输出。

zeroCPR 框架可以概述如下,我们对每个产品应用以下 3 个步骤:

zeroCPR 框架,图片由作者提供

1. 列出互补产品

如前所述,首先需要解决的瓶颈是找到实际的互补产品。由于相似性模型不可行,我们需要使用 LLM。第一步的执行非常简单。给定一个输入产品(例如可口可乐),生成一个用户可能购买的有效互补产品列表。

我已经要求 LLM 使用 Python 输出一个完全可解析的列表:一旦解析完成,我们就可以可视化输出结果。

有效补充产品列表,图片来源:作者

结果其实并不差:这些都是很可能与可口可乐(Coca-Cola)一起购买的产品。然而,有一个小问题:这些产品可能不在我们的数据中

2. 匹配我们数据中的可用产品

下一步是尝试将 LLM 建议的每个补充产品与我们数据集中的相应产品匹配。例如,我们想将“玉米片(Nachos)”与我们数据集中最接近的产品进行匹配。

我们可以使用向量搜索来执行这种匹配。对于每个 LLM 产品,我们将与数据集中语义上最相似的产品进行匹配。

相似度匹配,图片来源:作者

如我们所见,结果远非准确。“玉米片(Nachos)”将与“盐和胡椒蘑菇套装(SET OF SALT AND PEPPER TOADSTOOLS)”匹配,而与“汉堡(Burgers)”最接近的匹配是“沙滩小屋凳子(S/2 BEACH HUT STOOLS)”。其中一些匹配是有效的(例如餐巾纸),但如果没有有效的匹配,语义搜索仍然会将其与一个无关的候选项匹配。根据经验,使用余弦相似度阈值来选择有效的匹配是一种糟糕的方法。相反,我将再次使用 LLM 来验证数据。

3. 使用数据框链(Chain-of-DataFrame)选择正确的补充产品

现在的目标是验证上一步的匹配结果。我第一次尝试将 LLM 推荐的产品进行匹配时,由于输出缺乏一致性,我感到非常沮丧。尽管是一个 70B 的模型,当我传入一个待匹配的产品列表时,输出结果却不尽如人意(格式上有错误,并且输出非常不现实)。

然而,我注意到,通过输入一系列产品并要求模型对每个样本进行推理输出一个分数(0 或 1):(遵循 pandas 数据框的格式,并对单列应用转换),模型在格式和输出上要可靠得多。我称这种提示方法为数据框链(Chain-of-Dataframe),这是对广为人知的 pandas 数据结构的参考:

数据框链(Chain-of-DataFrame),图片来源:作者

为了给你展示数据框链提示的思路。在下面的示例中,{product_name} 是可口可乐(Coca-Cola),而{complementary_list} 则是下图中我们可以看到的名为“recommended_product”的

A customer is doing shopping and buys the following product
product_name: {product_name}

A junior shopping expert recommends the following products to be bought together, however he still has to learn:
given the following
complementary_list: {complementary_list}

Output a parsable python list using python, no comments or extra text, in the following format:
[
    [<product_name 1>, <reason why it is complementary or not>, <0 or 1>],
    [<product_name 2>, <reason why it is complementary or not>, <0 or 1>],
    [<product_name 3>, <reason why it is complementary or not>, <0 or 1>],
    ...
]
the customer is only interested in **products that can be paired with the existing one** to enrich his experience, not substitutes
THE ORDER OF THE OUTPUT MUST EQUAL THE ORDER OF ELEMENTS IN  complementary_list

Take it easy, take a big breath to relax and be accurate. Output must start with [, end with ], no extra text

输出是一个多维列表,可以轻松解析,并立即转换回pandas 数据框

数据框链输出(Chain-of-Dataframe),图片来源:作者:Michelangiolo Mazzeschi

注意模型生成的推理分数列,用于找到最佳补充产品。通过这一步骤,我们能够筛选出大多数无关的匹配项。

***该算法可能看起来与CHAIN-OF-TABLE: EVOLVING TABLES IN THE REASONING CHAIN FOR TABLE UNDERSTANDING相似,但我认为上面提出的算法要简单得多,并使用了不同的结构。如果你有不同的看法,欢迎评论。

4. 处理少量数据:最近替代品填充

一个最后需要解决的问题。由于数据不足,推荐的产品数量可能非常少。在上面的示例中,我们可以推荐 6 个互补产品,但可能会出现只推荐 2 或 3 个的情况。在数据受限的情况下,我们如何改善用户体验,并扩展有效推荐的数量呢?

最近替代品填充,图片来源:作者

我想到的一个解决方案,像往常一样,十分简单。zeroCPR 的输出是所有互补产品(如上图中的第一行所示)。为了填补缺失的推荐项,我们可以通过语义相似性找到每个互补产品的 k 个替代品。

运行 zeroCPR 引擎

您可以参考以下示例笔记本来顺利运行代码:克隆整个代码库并保持文件结构,笔记本应当自动调整至正确路径并运行。

在这个代码库中,我使用了来自以下 Kaggle 数据集的产品列表。列表中的产品越多,搜索准确的机会就越大。目前,库仅支持GroqCloud

同时,请记住,框架在 70B 模型上表现良好,并没有设计或测试以与小型语言模型(例如 llama3-8B)在性能上匹配。

from zeroCPR import engine

# initiate engine
myagent = engine.agent(groq_api_key='<your_api_key>')

下一步是准备一个产品列表(以 Python 列表格式)。

df = pd.read_excel('notebooks/df_raw.xlsx')
product_list = list(set(df['Description'].dropna().tolist()))
product_list = [x for x in product_list if isinstance(x, str)]
product_list = [x.strip() for x in product_list]
product_list

>>>
['BLACK AND WHITE PAISLEY FLOWER MUG',
 'ASSORTED MINI MADRAS NOTEBOOK',
 'VICTORIAN METAL POSTCARD CHRISTMAS',
 'METAL SIGN EMPIRE TEA',
 'RED WALL CLOCK',
 'CRYSTAL KEY+LOCK PHONE CHARM',
 'MOTORING TISSUE BOX',
 'SILK PURSE RUSSIAN DOLL PINK',
 'VINTAGE SILVER TINSEL REEL',
 'RETRO SPOT TRADITIONAL TEAPOT',
...

该库利用了sentence-transformers来将列表编码为向量,因此您无需自己实现此功能:

df_encoded = myagent.encode_products(product_list)

>>>
100%|██████████| 4678/4678 [02:01<00:00, 38.47it/s]

查找单个产品的互补项

这个功能是为了测试引擎在单个产品上的表现(这是一个非常廉价的测试,但结果令人印象深刻)。

df_candidates, df_filtered = myagent.find_product_complementaries(['pizza'])
display(df_filtered)

单个商品的互补产品,图片来源:作者

查找列表的互补项

整个库的核心包含在这个函数中。该代码对一个列表运行前述的功能(它可以包含 10 个,甚至 1000 个产品),并构建一个包含所有互补产品的数据框。之所以这个函数与前一个不同,不仅因为它可以接受列表作为输入(否则我们可以简单地在前一个库中使用一个 for 循环)。

df_complementaries = myagent.find_product_complementaries(product_list[50:60])

>>>
** SET 3 WICKER LOG BASKETS
** FUNKY GIRLZ ASST MAGNETIC MEMO PAD
** BLACK GEMSTONE BRACELET
ERR
** TOY TIDY PINK POLKADOT
** CROCHET WHITE RABBIT KEYRING
** MAGNETS PACK OF 4 HOME SWEET HOME
** POPCORN HOLDER , SMALL
** FLOWER BURST SILVER RING GREEN
** CRYSTAL DIAMANTE EXPANDABLE RING
** ASSORTED EASTER GIFT TAGS

由于我们在处理大型语言模型(LLM),这个过程有时可能会失败:我们不能让这个不便打破代码。这个函数被设计为对每个样本进行反复试验和错误的迭代过程。正如你从输出中看到的,当尝试为“黑色宝石手链”寻找互补产品时,第一次迭代失败了(可能是因为解析错误,或者可能是HTTPS 请求失败)。

该管道被设计为在放弃一个产品并继续下一个之前,最多尝试 5 次。

最终输出:输入产品的互补列表,图片来自作者

在这个过程的结束时,我将输出的数据框保存到一个文件中。

df_complementaries.to_parquet('notebooks/df_complementaries.parquet', index=None)

享受吧!

这个库旨在解决在缺乏数据的情况下寻找互补产品的问题,这是新兴企业中常见的难题。结合其主要目标,它还展示了如何在结构化数据上使用大型语言模型,而不需要繁琐的提示调优。

拥有数据的企业与刚刚起步的企业之间存在巨大的差距。这类算法的目标是缓解这种差异,甚至让小型初创公司也能访问到企业级技术。

这是我基于最新实验之一开发的第一个开源库之一。带着一些好运,这个库有可能成长为一个更大的项目,吸引更多用户。我希望你喜欢这篇文章,且提供的代码能为你提供帮助。祝你好运!

《利用混合整数规划优化航空旅行网络简介》

原文:towardsdatascience.com/introduction-0dd288ba9738?source=collection_archive---------7-----------------------#2024-02-18

如何设计一种算法,以最具成本效益的方式在网络中安排乘客需求

Rohan NandaTowards Data Science Rohan Nanda

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

--

简介:

这是一个关于车辆路径问题的变种,但它被调整为适应航空运输网络,即起始地到目的地问题。

图片由NASA提供,来源:Unsplash

首先一点背景:航空公司常常面临如何解决城市间需求的问题——是开设直飞航班,还是提供中转航程,使得需求通过一个或多个枢纽进行疏导?从乘客的角度来看,后者当然更可取,但对航空公司来说,这样做成本更高,因此风险也更大——如果航班没有满员怎么办?运营一条航线非常昂贵。换句话说,我们正在尝试做这个*:

图 1:我们试图解决的问题的示意图。左侧是起始地-目的地(OD)需求,而右侧是航程(带有相关的流量路由),可以看作是市场对需求的响应。

*图论爱好者会将其视为图稀疏化问题的一个特殊案例,最近这个问题受到了相当多的关注。

该行业通常使用所谓的行程选择模型来解决这个问题,这些模型仅仅是概率模型,用于根据连接数、路线长度、飞行时间等因素来确定乘客偏好的路线……当网络形态已经固定时,这种方法效果很好,但决定开通哪些路线就更加复杂了。这是因为有一些路线只有在能够吸引来自其他来源的足够连接流量时才是可行的——而这一点只有在没有直接路线能够服务该流量时才能发生。换句话说,每条路线的状态取决于其邻近路线的状态,从而将问题转化为一个组合优化问题。

这正是混合整数规划(MIP)设计用来解决的问题!具体来说,我们将提出一个问题,反映以下行为:网络流量守恒边激活成本以强制稀疏化。

简单问题:

在本文的其余部分,我将使用一个简单的示例进行说明。为了完全描述这个问题,我们需要以下输入:

输入图:

一个密集的起点-终点双向图 G = (V, E),其中 n 是顶点 V 的数量,m 是边 E 的数量。每条边都有起点-终点需求(O 和每对城市间的距离(Distance)作为属性。通常,需求遵循帕累托分布,即少数边具有高需求,而其余边具有低需求*:

图 2:输入需求图(左)和按边分布的需求(右)

通过随机实例化节点的坐标及其人口生成的图。使用所谓的引力模型进行运输,可以获得一个现实的需求曲线。更多信息,请参见 链接*

成本假设:

根据边的距离和将分配的典型车辆类型,每条边将具有以下成本属性:

  • 每位乘客的成本,Costₚₐₓ,其中 pax 是乘客的缩写。实际上,应该考虑的是每座位的成本,而不是每乘客的成本,因为并不是每辆车都一定是满员的。然而,这将需要对每辆车进行离散建模(并且需要一个相关的整数变量),这会导致问题规模急剧增大。

  • 操作一条路线的最小成本,Costₘᵢₙ。可以将其视为边激活成本。

请记住,CostₚₐₓCostₘᵢₙ 都是 m × 1 向量(每条边一个),且两者的成本都与距离成线性比例关系。

有了这些,我们就具备了设计 MIP 所需的所有条件。如你所猜测的那样,目标是最小化系统的成本函数,同时遵守网络流量约束。

网络流量守恒

这是一个著名的条件,表明每个顶点的流入和流出必须平衡,除非它是源点或汇点:

这里的 (i, j, k) 是顶点索引。个人而言,我并不太喜欢这种表示方法,更喜欢使用图论中的边关联矩阵概念来表达等效的公式。通常,边关联矩阵用 n × m 的矩阵 B 来表示,其中每一行的条目除对应边的关联顶点外,其它均为零,关联顶点的值为 1 和 -1,分别表示源点和汇点:

如果我们初始化一个 m × m 的变量矩阵(我们称之为 R,表示行程路线——见图 1)来表示 G 中每个需求边的流量路线,我们可以等价地通过以下方式来表达上述条件:

其中,diag(O) 是一个 m × m 矩阵,其每个对角线元素对应于边 i 的需求。如果你展开右侧任意一行 i,立刻就能明白为什么任何满足这个方程的 R 从流量守恒的角度来看是有效的。

但是需要注意的是,BR 都是有方向的。在我们的成本函数的背景下,我们并不关心某些流量是否为负值——我们只关心边 i 上流动的乘客的绝对总数,以便量化运输它们的费用。为了表示这一点,我们可以定义 m × 1 的腿向量 L

有了这些定义,我们得到了一个与网络流量守恒原理兼容的函数映射 O → L。从此以后,L 代表每条边上的总乘客流量。

边激活

这就是问题的核心!考虑到如果 费用ₘᵢₙ=0,解决方案将是显而易见的,L 将与 O 一一对应映射。因为任何替代路线都必然比直线路程更长,所以最便宜的选择总是后者。然而,在存在 费用ₘᵢₙ 的情况下,存在一个权衡,即长途旅行带来的 △费用 与通过激活边所带来的 △费用。换句话说,我们需要每条边的费用配置文件为:

图 3:带有边激活费用的成本函数示意图

这个函数有 3 部分:

  • 如果乘客数量为零,则不产生任何费用(费用 = 0

  • 如果乘客数量在 0 和阈值之间,无论乘客数量是多少,都会产生固定费用(费用 = Cₘᵢₙ)。

  • 如果乘客数量超过阈值,费用将根据每位乘客的成本线性增长(费用 = Cₚₐₓ.L

如果不是因为零点不连续性,这将是一个相当简单的问题可以解决。然而,实际上我们遇到了一个非凸组合优化问题,因为在某些边上,乘客数量是零与否会导致行为的突然变化。在这种情况下,我们需要一个激活(即二进制)变量来告诉算法应遵循哪种条件。使用大 M 法,我们可以将其表述如下:

其中,m × 1 的二进制变量z(即z ∈ [0,1])表示某条路线是否开放,以及一个非常大的标量变量M。如果你不熟悉大 M 法,可以在这里了解。在这个上下文中,它仅仅是强制执行以下条件:

  • Lᵢ = 0 zᵢ=0

  • Lᵢ >0 zᵢ=1

理想情况下,我们希望通过简单地将成本函数与这个激活变量相乘来指示应遵循哪种成本行为。然而,这样会使约束变为非线性,解起来非常复杂。相反,我们可以再次使用大 M 法,这次将问题线性化,同时达到相同的效果:

将成本最小化目标与≥不等式结合,我们基本上得到了一个 minmax 问题,其中:

  • zᵢ=0 → Costᵢ = minmax(0, -M) = 0

  • zᵢ=1 → Costᵢ = minmax(Cₘᵢₙ, CₚₐₓL)

就是这样!问题的完整公式如下:

我们现在只需要输入一些数字,就能看到这个“魔法”的发生。

对最小阈值的敏感性

从描述中应该可以清楚看出,最小阈值是这里的主要关注点,因为它定义了稀疏化的程度。看到逐渐提高阈值的影响非常有趣:

图 4:从左到右,应用低、中和高阈值对边缘成本的影响

请注意,无论阈值如何,图形始终保持连接——这是网络流量守恒原理的结果,确保所有需求都能得到满足。另一种形象化的方法是观察沿边的需求分布:

图 5:输入成本函数与输出流量分布的低、中和高阈值

在这里,我们可以看到阈值越高,整合的程度越高(更少的路线有更大的交通流量),同时对应有更多的没有流量的路线。

结论

这只是一个简单的介绍,实际上这是一个非常复杂的问题(航空公司网络的复杂性远不止最低阈值成本)。尽管如此,它展示了真实网络的核心行为之一,同时为制定整数线性规划(MIPs)问题提供了一些关键概念的基本介绍。相关代码可以在我的Github上找到,欢迎尝试。

如果你尝试运行它,你很快就会注意到,求解时间会随着图中顶点数量的增加而呈指数级增长。特别是当你使用 cvxpy——一个常见的(但基础的)开源 Python 库用于简单优化问题时,情况尤为明显。尽管如此,即便是复杂的商业求解器也很快会遇到它们的极限。这是组合优化问题的不可避免的真相;它们扩展性差,且在问题规模超出一定范围后往往不再实用。

在下一篇文章中,我将介绍一种方法,通过使用图神经网络作为代理模型,尝试抽象化一些复杂性。

除非另有说明,否则所有图片均由我,作者本人提供。

Apache Iceberg 简介

原文:towardsdatascience.com/introduction-to-apache-iceberg-ef0358634c1b?source=collection_archive---------2-----------------------#2024-02-29

探讨 Apache Iceberg 的优缺点,以及它如何帮助构建你自己的 Lakehouse。

Pier Paolo IppolitoTowards Data Science Pier Paolo Ippolito

·发表于数据科学前沿 ·5 分钟阅读·2024 年 2 月 29 日

--

图片来源:Alexander HafemannUnsplash

介绍

随着 GCP、Azure 和 AWS 等云服务提供商使数据湖(Data Lakes)变得易于访问,越来越多的组织能够廉价地存储其非结构化数据。尽管数据湖有许多限制,例如:

  • 在混合批处理和流式处理或附加新数据时,可能会出现不一致的读取情况。

  • 对现有数据的细粒度修改可能变得复杂(例如,为了满足 GDPR 要求)

  • 在处理数百万个小文件时性能下降。

  • 不支持 ACID(原子性、一致性、隔离性、持久性)事务。

  • 没有模式强制执行/演化。

为了尝试缓解这些问题,Apache Iceberg 由 Netflix 于 2017 年提出。Apache Iceberg 是一种表格格式,能够为支持 ACID 事务、时间旅行等提供额外的抽象层,同时与各种数据源和工作负载一起使用。表格格式的主要目标是定义一种协议,指导如何最好地管理和组织构成表格的所有文件。除了 Apache Iceberg,还有其他...

Python 中的因果推断与机器学习介绍

原文:towardsdatascience.com/introduction-to-causal-inference-with-machine-learning-in-python-1a42f897c6ad?source=collection_archive---------0-----------------------#2024-01-24

探索在 Python 中应用的因果机器学习的概念和基本方法

Marco PeixeiroTowards Data Science Marco Peixeiro

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

--

图片由David Clode提供,来自Unsplash

因果推断在各种情境中有许多实际应用,但根据我的经验,它是一个在数据科学家中很少被讨论的话题。

在本文中,我们定义了因果推断并阐明了它的使用动机。然后,我们应用一些基本的 Python 算法来衡量某种现象的影响。

定义因果推断

因果推断是一个研究领域,旨在衡量某种治疗的效果。

另一种思考因果推断的方法是,它回答假如类的问题。目标始终是衡量某种行为所带来的影响。

用因果推断回答的问题示例包括:

  • 广告活动对产品销量的影响是什么?

  • 提价对销量的影响是什么?

  • 这种药物能让病人更快康复吗?

我们可以看到,这些问题对于决策者来说非常相关,但无法通过传统的机器学习方法来解决。

《气候变化的计算机视觉导论》

原文:towardsdatascience.com/introduction-to-computer-vision-for-climate-change-81d888f471bd?source=collection_archive---------6-----------------------#2024-05-02

Daniel Pazmiño VernazaTowards Data Science Daniel Pazmiño Vernaza

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

--

我的学生和我正在测量科托帕希冰川的退缩情况。

我出生并成长在厄瓜多尔。在这个国家,天气和气候塑造着我们的生活。例如,我们的能源供应依赖于充足的降雨来提供水力发电。小时候,我记得经常发生停电。不幸的是,厄瓜多尔的韧性不足。在我写这篇文章时,我们再次经历了停电。悖论的是,厄尔尼诺现象每年都会带来洪水。我喜欢徒步旅行,怀着极大的悲伤,我看到我们的冰川正在退缩。

十年前,我决定攻读气象学博士学位。气候变化及其影响让我感到忧虑。这是人类在本世纪面临的一项艰巨挑战。我们在科学理解这一问题方面取得了巨大进展,但仍然需要更多的行动。

当我开始攻读博士学位时,很少有研究人员使用人工智能(AI)技术。如今,人们普遍认同,利用人工智能的潜力能够带来改变,特别是在减缓和适应气候变化方面。

机器学习,特别是计算机视觉(CV)使我们能够理解大量可用数据。这种能力将使我们能够采取行动。揭示视觉数据(例如卫星数据)中隐藏的模式是应对气候变化的关键任务。

本文介绍了计算机视觉(CV)及其与气候变化的交集。这是该主题系列文章中的第一篇。文章共有五个部分。首先,它介绍了背景。接着,文章定义了一些与计算机视觉相关的基本概念。然后,文章通过案例研究探讨了计算机视觉在应对气候变化中的能力。之后,文章讨论了挑战和未来发展方向。最后,概括部分提供了总结。

理解计算机视觉

计算机视觉使用计算方法从图像中学习模式。地球观测(EO)主要依赖卫星图像。因此,计算机视觉是进行气候变化分析的一个非常合适的工具。为了从图像中理解气候模式,使用了多种技术。其中一些最重要的技术包括分类、目标检测和图像分割。

分类:涉及根据预定义的类别(单标签)对(单一)图像进行分类。火灾检测和烧毁区域映射使用图像分类技术对卫星图像进行处理。这些图像提供了与烧毁植被相关的光谱特征。通过这些独特的模式,研究人员可以追踪野火的影响。

目标检测:包括在感兴趣的区域内定位对象。飓风和气旋的追踪就使用了这一技术。检测其云层模式有助于减少其对沿海地区的影响。

图像分割:为图像中的每个像素分配一个类别。该技术有助于识别区域及其边界。图像分割也称为“语义分割”。由于每个区域(目标类别)都会收到一个标签,因此它的定义中包含“语义”。例如,追踪冰川的退缩就使用了这一技术。通过分割冰川的卫星图像,可以追踪其变化。例如,监测冰川的范围、面积和体积随时间的变化。

本节提供了一些关于计算机视觉(CV)在应对气候变化中的应用实例。接下来的章节将分析这些实例作为案例研究。

案例研究 1:野火检测

信用:Issy Bailey(Unsplash)

气候变化对野火有多个影响。例如,它增加了极端事件发生的可能性,还延长了火灾季节的持续时间。同样,它还会加剧火灾的强度。因此,投入资源开发创新的解决方案以预防灾难性野火是至关重要的。

这种类型的研究依赖于图像分析来进行野火的早期检测。一般来说,机器学习方法在预测这些事件方面被证明是有效的。

然而,先进的人工智能深度学习算法能产生最佳结果。一个这样的先进算法例子是神经网络(NNs)。神经网络是受人类认知启发的机器学习技术。该技术依赖一个或多个卷积层来检测特征。

卷积神经网络(CNN)在地球科学应用中非常流行。CNN 展现了提高火灾检测准确性的巨大潜力。多个模型使用了这一算法,例如 VGGNet、AlexNet 或 GoogleNet。这些模型在计算机视觉任务中展现了更高的准确性。

通过计算机视觉算法进行火灾检测需要图像分割。然而,在进行数据分割之前,需要进行预处理。例如,减少噪声、归一化值和调整图像大小。接下来,分析将标记出表示火灾的像素,从而将其与其他图像信息区分开来。

案例研究 2:气旋追踪

图片来源:NASA(Unsplash)

气候变化将增加气旋的频率和强度。在这种情况下,实时应用无法处理大量的数据。例如,来自模型、卫星、雷达和地面气象站的数据。计算机视觉(CV)在处理这些数据时显示出高效性。它还减少了与人为干预相关的偏差和错误。

例如,数值天气预测模型仅使用了 3%–7%的数据。在这种情况下,使用的是来自静止轨道环境卫星(GOES)的观测数据。数据同化过程使用的数据甚至更少。CNN 模型从这大量的图像中选择最相关的观测数据。这些观测数据涉及到气旋活跃(或即将活跃)的兴趣区域(ROI)。

确定这个兴趣区域(ROI)是一个分割任务。地球科学中使用了多种模型来处理这一问题。然而,U-Net CNN 是最受欢迎的选择之一。该模型设计与医学分割任务相关,但也已证明在解决气象问题上非常有效。

案例研究 3: 追踪冰川后退

图片来源:Ryan Stone(Unsplash)

冰川是气候变化的温度计。气候变化对冰川的影响是直观的(如冰川轮廓的后退)。因此,它们象征着气候变异和变化的后果。除了视觉影响外,冰川后退还带来了其他后果。例如,对水资源可持续性的负面影响;水电发电的不稳定;饮用水质量的影响;农业生产的减少;生态系统的失衡。从全球范围来看,甚至海平面上升也威胁到沿海地区。

监测冰川的过程曾经非常耗时。卫星图像的解读需要专家进行数字化和分析。计算机视觉(CV)可以帮助自动化这一过程。此外,计算机视觉还可以提高过程的效率。例如,允许将更多数据纳入建模中。像 GlacierNet 这样的 CNN 模型利用深度学习的力量来追踪冰川。

有多种技术可以用来检测冰川边界。例如,图像分割、目标检测以及边缘检测。计算机视觉还可以执行更复杂的任务。例如,比较冰川图像随时间变化的情况。还可以确定冰川的移动速度,甚至是冰川的厚度。这些都是跟踪冰川动态的强大工具。这些过程能够为适应性措施提供有价值的信息。

挑战与未来方向

采用计算机视觉(CV)应对气候变化面临着一些特定的挑战。讨论每一个挑战可能需要一本书的篇幅。然而,这里的目标较为 modest。我将尝试将这些挑战列举出来,供参考。

  • 数据复杂性: 使用多种数据源的需求及其固有的复杂性。例如,卫星和航拍图像、激光雷达数据以及地面传感器。数据融合是一项正在发展的技术,旨在解决这一挑战性问题。

  • 模型可解释性: 当前的挑战之一是开发混合模型。这意味着将统计数据驱动模型与物理模型相结合。将我们对气候系统的知识融入计算机视觉算法,可以提高其可解释性。因此,这些模型在拟合复杂函数时表现出色,但也应能提供对基础因果关系的理解。

  • 标注样本: 高质量标注样本的可用性。这些样本应当针对遥感(EO)问题,用于训练计算机视觉模型。生成这些样本是一个费时且昂贵的任务。解决这个挑战是当前的一个活跃研究领域。

  • 伦理: 在人工智能(AI)开发中融入伦理考量是一个挑战。隐私、公平性和问责制在确保与利益相关者的信任中发挥着关键作用。在气候变化背景下,考虑环境正义也是一种明智的策略。

总结

计算机视觉是应对气候变化的强大工具。从检测野火到追踪气旋形成和冰川退缩,计算机视觉正在改变监测、预测和预示气候影响的方式。这些影响的研究依赖于计算机视觉技术。例如,分类、目标检测和图像分割。最终,在计算机视觉与气候变化的交集处会出现若干挑战。例如,如何管理多源数据、提升机器学习模型的可解释性、生成高质量标注样本以训练计算机视觉模型,以及在设计人工智能系统时考虑伦理问题。后一篇文章将介绍如何收集和整理图像数据集,尤其是那些与气候变化相关的数据集。

参考文献

  • Kumler-Bonfanti, C., Stewart, J., Hall, D., & Govett, M. (2020). 使用深度学习进行热带和温带气旋检测。应用气象学与气候学期刊59(12),1971–1985。

  • Maslov, K. A., Persello, C., Schellenberger, T., & Stein, A. (2024). 通过深度学习和开放地球观测数据实现全球冰川制图。arXiv 预印本 arXiv:2401.15113.

  • Moumgiakmas, S. S., Samatas, G. G., & Papakostas, G. A. (2021). 无人机上的火灾检测计算机视觉 — 从软件到硬件。未来互联网, 13(8), 200.

  • Rolnick, D., Donti, P. L., Kaack, L. H., Kochanski, K., Lacoste, A., Sankaran, K., … & Bengio, Y. (2022). 用机器学习应对气候变化。ACM 计算调查 (CSUR), 55(2), 1–96.

  • Tuia, D., Schindler, K., Demir, B., Camps-Valls, G., Zhu, X. X., Kochupillai, M., … & Schneider, R. (2023). 人工智能推动地球观测发展:一个展望。arXiv 预印本 arXiv:2305.08413.

可解释聚类简介

原文:towardsdatascience.com/introduction-to-interpretable-clustering-d0e07fbd2c99?source=collection_archive---------3-----------------------#2024-08-01

图片由Sergei A提供,来自Unsplash

什么是可解释的聚类,为什么它很重要?

Nakul UpadhyaTowards Data Science Nakul Upadhya

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

--

聚类是一个流行的无监督学习任务,用于将相似的数据点分组。尽管这是一个常见的机器学习任务,但大多数聚类算法并没有解释每个聚类的特征或为什么某个点会归类于某个聚类,这需要用户进行大量的聚类分析。随着数据集的增大,这一费时的过程变得异常困难。很讽刺的是,聚类的主要用途之一就是发现数据中的趋势和模式。

考虑到这些因素,难道不希望有一种方法不仅能够聚类数据,还能同时提供每个聚类的固有特征吗?这就是可解释聚类领域的作用所在。这些方法构建了一个模型,将数据点映射到聚类上,理想情况下,用户可以分析这个模型,找出每个聚类的特征。在本文中,我将讨论为什么这个领域很重要,并介绍一些可解释聚类的主要方向。

如果你对可解释的机器学习以及其他伦理人工智能方面感兴趣,可以考虑查看我的其他文章并关注我!

Kaggle 简介及如何在 Titanic 竞赛中获得前 7%的成绩

原文:towardsdatascience.com/introduction-to-kaggle-and-scoring-top-7-in-the-titanic-competition-7a29ce9c24ae?source=collection_archive---------6-----------------------#2024-04-23

开始使用 Kaggle 并提交一个(不错的)初步解决方案

Oliver STowards Data Science Oliver S

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

--

Kaggle是一个有趣的平台,举办各种数据科学和机器学习竞赛,涵盖了如体育能源自动驾驶等主题。

在这篇文章中,我们将介绍 Kaggle,并解决入门级的“Titanic”挑战。我们将解释如何处理并解决这样的挑战,并通过一个“Titanic”竞赛的前 7%解决方案进行演示。

图片由Derek Oyen提供,来源于Unsplash

你可以在Github上找到完整的代码,并在阅读本文时跟随代码,也可以复制我的准确分数。在这篇文章中,我们遵循了一些我认为的 Python 最佳实践,并使用了一些有用的工具,如 mypy 和 poetry。话不多说,让我们直接开始吧。

Kaggle

Kaggle 提供了种类繁多的数据科学/机器学习竞赛,具体例子请参见介绍部分。这是一个测试和提高你数据科学/机器学习知识的绝佳途径,同时还能学习如何动手解决问题。而且,你甚至可以赢得现金奖励!然而,Kaggle 聚集了许多顶尖的数据科学家和机器学习专家——奖励只会颁发给少数几个最佳解决方案(在数百个或数千个解决方案中)——因此,在这里获胜是...

线性编程简介 — 第二部分

原文:towardsdatascience.com/introduction-to-linear-programming-part-ii-4fa9521ac3a7?source=collection_archive---------2-----------------------#2024-07-02

使用 R 进行生产优化

Robert LohneTowards Data Science Robert Lohne

·发布于 Towards Data Science ·14 分钟阅读·2024 年 7 月 2 日

--

去年,我的一位朋友找到了我,他在一家小型的家族钢铁和金属公司工作。他想知道是否可以创建某种工具,帮助他解决切割钢梁时最小化浪费的问题。听起来像是线性编程的问题!

当我开始的时候,关于如何在 R 中使用线性编程的初学者文章并不多,而且很难找到对没有太多数学基础的人有意义的内容。到 2023 年初,线性编程与 R 的结合也是 ChatGPT 不擅长的领域,所以我常常希望有一本指南。

这个系列是我尝试制作这样的指南。希望它对某些人有所帮助。

这是第二部分,如果你正在寻找 R 中线性编程的介绍,请查看第一部分

可是我不懂数学呀..!?

如果你读过线性编程或线性优化背后的理论,你会发现有大量的数学内容。如果你没有数学背景,这可能会让人却步(我没有)。对我来说,这主要是因为我从未上过足够的数学课程来理解许多符号。最初,这使得理解与线性编程相关的教程比应该的更加困难。然而,你不需要理解理论背后的数学,就能应用本文中代码的原则。

最大似然估计介绍

原文:towardsdatascience.com/introduction-to-maximum-likelihood-estimates-7e37f83c6757?source=collection_archive---------4-----------------------#2024-09-18

通过其在下一个单词预测中的应用来了解最大似然估计

Rishabh MisraTowards Data Science Rishabh Misra

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

--

图片由freestocks提供,来源:Unsplash

介绍

最大似然估计(MLE)是一种基本方法,能够使任何机器学习模型从可用数据中学习到独特的模式。在这篇博客中,我们将通过将其应用于下一个单词预测问题来学习最大似然估计的概念,以使解释更加直观。

注意:本文将使用一些来自概率论和机器学习理论的概念。我会尽力将内容保持尽可能通用,并在引入这些概念时提供相关背景阅读的链接。

问题设定

一个优秀键盘应用的常见特性之一是预测即将输入的单词。随着用户使用应用的增加,这些预测会越来越准确,从而节省用户的输入时间。文本预测的另一个应用场景是在搜索引擎中。预测性搜索可以节省输入时间并引导访问者到结果,而不是让他们输入略有偏差的搜索内容,进而无法返回大量的结果。作为这些应用的用户,我相信你曾经想过:“这到底是如何工作的…

在 Markdown 中介绍 Mermaid 图表

原文:towardsdatascience.com/introduction-to-mermaid-graphs-in-markdown-080d4377cab4?source=collection_archive---------2-----------------------#2024-06-04

摄影师 Wexor Tmg 摄于 Unsplash

丰富您的 GitHub 文档

从简单的代码创建可视化

Zolzaya LuvsandorjTowards Data Science Zolzaya Luvsandorj

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

--

Mermaid 是一个简单而多功能的基于 JavaScript 的图表和绘图工具,允许用户仅通过在 Markdown 中输入代码就能创建多种类型的图表。由于其语法自动渲染为图表,并与 Markdown 无缝集成,因此无需维护其他可视化文件。如果您希望通过使用 Mermaid 来增强您的 GitHub 仓库文档,使其更易于理解且更具吸引力,那么这篇实用的文章将向您展示 13 种有用的 Mermaid 图表。

📍 0. 概述

本篇文章假设您已经熟悉 Markdown 的基础。如果您需要复习,建议先阅读 这篇文章

若要练习使用本文中的示例代码,您可以选择以下任一方式:

  • 您最喜欢的 Markdown 编辑器,我推荐的做法(您需要确保您的 Markdown 编辑器支持 Mermaid 语法:例如,Markdown Preview Mermaid Support 扩展将允许您在 Visual Studio Code 中渲染图表)或

  • Mermaid 实时编辑器

Python 的多阶段镜像构建简介

原文:towardsdatascience.com/introduction-to-multi-stage-image-build-for-python-41b94ebe8bb3?source=collection_archive---------0-----------------------#2024-04-21

本文介绍了多阶段构建方法,用于设置轻量级的 Python 开发环境。

Rami KrispinTowards Data Science Rami Krispin

·发布于 Towards Data Science ·阅读时间 9 分钟·2024 年 4 月 21 日

--

在我开始使用容器将代码部署到 Github Actions 时,我从未过多关注我的镜像大小。这里的数学原理很简单:容器的大小越大,加载时间越长,因此成本也越高。当我的 Python 镜像大小达到了 5GB(感谢 PyTorch!)时,我开始探索更高效的镜像构建方法。

简而言之——我通过多阶段构建方法将基线镜像的大小减少了 65%。

在这篇文章中,我们将回顾三种构建方法,看看通过几个简单的步骤,如何减少 Python 镜像的大小。我们将从使用官方 Python 镜像python:3.1o的基线构建开始,然后探索镜像精简版本,最后介绍一种更高级的方法——多阶段构建。

一艘满载集装箱的货船(由作者使用 Midjourney 制作)

先决条件

要跟随本教程,你将需要以下设置:

  • Docker Desktop(或等效工具),如果你使用的是 macOS 或 Windows 操作系统,或者如果你使用的是 Linux 系统,则需要安装 Docker...

目标贝叶斯假设检验介绍

原文:towardsdatascience.com/introduction-to-objective-bayesian-hypothesis-testing-06c9e98eb90b?source=collection_archive---------6-----------------------#2024-06-11

如何利用默认贝叶斯因子推导假设的后验概率

Ryan BurnTowards Data Science Ryan Burn

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

--

卡拉马祖精神病医院——Cushny 和 Peebles 进行氟烯丙胺临床试验的地方 [14]

假设检验贯穿于统计学教育和科学研究,并且有充分的理由。实验通常是从假设出发,或者产生的数据导致产生一个假设。例如,考虑以下来自药理学家 Cushny 和 Peebles 的数据集[1]:

Cushny 和 Peebles 希望找到一种有效的催眠药。为此,他们在密歇根州卡拉马祖的疯人院给病人服用了不同相关药物的小剂量,并测量了睡眠活动。他们的数据集在某种程度上很有名[2]。这是学生[9]首次应用 t 分布的例子之一,后来由 Ronald Fisher 在他那本具有高度影响力的著作《研究工作者的统计方法 [15, p. 119]》中进行了分析。

看数据时,几个自然假设自然而然地出现:L-氟烯丙胺 HBr 在诱导睡眠方面是否比 L-莨菪碱 HBr 更有效?L-莨菪碱 HBr 是否比没有药物更好…

强化学习简介及解决多臂赌博机问题

原文:towardsdatascience.com/introduction-to-reinforcement-learning-and-solving-the-multi-armed-bandit-problem-e4ae74904e77?source=collection_archive---------4-----------------------#2024-07-30

通过 Richard S. Sutton 的《强化学习》及定制 Python 实现进行分析,第一集

Oliver STowards Data Science Oliver S

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

--

强化学习(RL)是机器学习的一个迷人子领域。你可能已经从一些应用中了解过它,比如围棋[1]、自动驾驶[2]等。

在我看来,同样令人着迷的是 Sutton 和 Barto 的著名书籍《强化学习》[3]。我认为这本书是该主题的绝佳入门,但同时也深入探讨了该领域所有重要的理论话题。不过,书中的内容可能会有些繁重,尤其是在第一次阅读时,可能会显得有些数学化。

图片由卡尔·罗提供,来源于Unsplash

因此,我决定开始一系列文章,逐章总结这本书。我相信用不同的话语解释内容将大大帮助理解。我还会在 Python 中实现书中的所有(大部分)算法,并将它们应用于通过(曾经的)OpenAI Gym 框架[4]建模的问题和环境。就我所知,这两点目前是独特的,使得这个系列具有独特性。

本文是该系列的第一篇,将简要介绍强化学习的概况,然后快速概述 Sutton 的书籍结构——以及如何……

神经科学家空间分析细胞的入门(第一部分)

原文:towardsdatascience.com/introduction-to-spatial-analysis-of-cells-for-neuroscientists-part-1-82bef519ad46?source=collection_archive---------12-----------------------#2024-05-30

一种使用点模式分析(PPA)与spatstat的方式

Daniel Manrique-CastanoTowards Data Science Daniel Manrique-Castano

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

--

从点模式生成的密度核

作为一名神经科学家,近年来我一直致力于开发能够对大脑中细胞分布进行多模态评估的策略。我的动机是定量理解大脑损伤后神经胶质细胞的重排过程。在这个过程中,我遇到了spatstat(1),这是一个基于点模式的空间分析的多功能 R 包,称为点模式分析(PPA)。这种方法在地理学、流行病学或生态学等领域得到了很好的发展,但在神经生物学中的应用非常有限,甚至几乎没有。我最近发布了一个简短的协议(2),读者可以找到一篇预印本(3),其中详细介绍了这种方法的应用。

在这篇文章中,我的目标是为那些有兴趣揭示不同组织中细胞空间分布的研究人员提供一种易于理解的方法介绍,而不拘泥于科学论文中的叙述框架。

什么是点模式分析(PPA)?

PPA 是一种空间分析技术,用于研究给定区域(也称为观察窗口)内单个事件或物体的分布。该方法允许研究人员检查每单位面积内的物体数量(称为空间强度),以及这些点是否随机分布、聚集或规则间隔,并考察空间强度随不同协变量的变化。与原始且不可重复的细胞计数(例如 100 个细胞/mm²)不同,PPA 保留了所有空间信息,并允许对点模式进行多次且可重复的操作。这使研究人员能够识别影响感兴趣物体分布的潜在过程或结构。

PPA 的要求

执行点模式分析(PPA)的唯一要求是拥有单个物体(细胞、蛋白质、亚细胞结构等)的 xy 坐标。在本文中,我们重点介绍二维 PPA,尽管三维方法也可用。这些坐标随后使用 R 和 spatstat 函数进行处理,以创建点模式并将其存储为超框架(hyperframe)。

我使用 QuPath (4) 或 CellProfiler (5) 通过无偏的细胞检测/定量方法获取了单个细胞的坐标。我发现,圆形/圆状物体(如神经元,例如 NeuN)的检测和分割比不规则物体(如星形胶质细胞(GFAP)或小胶质细胞(IBA1))要容易,尤其是在细胞密度高且细胞重叠较多的情况下(例如,脑损伤后的胶质聚集)。不规则且高度聚集的物体的分割仍然是该领域的前沿问题。然而,QuPath 或 CellProfiler 提供了合理的准确性,最重要的是,它们是可重复的并且可以验证的。人工观察者也无法做得更好。因此,我建议如果在某些情况下你觉得某些物体仅对应于一个细胞的片段或多个细胞的组合,不必过于担心。调整参数以确保细胞检测/分割达到最佳效果。如果细胞间隔足够远(例如健康的大脑或细胞培养),那么不必担心。

创建点模式

在处理多个样本时,可以通过使用类似以下链接的函数来简化点模式的创建。该过程的核心是将包含单细胞坐标的单独.csv 文件转换为点模式(使用spatstatppp函数),并将其组织成一个超框架,可以将其保存并作为.rds R 对象共享。

在这里,我们将加载我在研究过程中创建的一个点模式(3)。该文件可以在GitHub 仓库中找到,文件名为PointPatterns_5x.rds。请随意用于研究、教育或培训目的。

library(brms)
library(dplyr)
library(ggplot2)
library(gtsummary)
library(modelr)
library(spatstat)
library(tidybayes)
PointPatterns <- readRDS("Data/PointPatterns_5x.rds")
row.names(PointPatterns) <- PointPatterns$ID 

head(PointPatterns)

你可以看到超框架包含了几列变量。我们将重点关注前三列,它们包含了三种类型的大脑细胞的点模式:神经元、星形胶质细胞和小胶质细胞。我们将以自己的方式重写一些变量列,以便练习 PPA 的实现。首先,我们来看一下通过同时绘制所有点模式(以神经元为例)来观察它们的样子:

plot(PointPatterns$Neurons)

图 1:多重点模式。

让我们通过查看任意一只大脑的细节来深入了解:

plot(PointPatterns$Neurons$M05)

图 2:单一的点模式。

我们可以稍微调整一下图形,通过在同一时间显示两种细胞类型(点模式),并改变它们的绘制方式(形状和颜色)。这里是一个示例:

# We plot neurons in black with symbol (10)
plot(PointPatterns$Neurons$M05, pch = 10, cex = 0.4, main = "Neurons and Astrocytes")

# We add astrocytes in red with a different symbol (18)
plot(PointPatterns$Astrocytes$M05, pch = 18, cex = 0.4, col = "red", add = TRUE)

图 3:不同细胞类型和美学效果。

这给出了细胞数量和分布的初步印象,但当然我们需要量化它。第一种方法是为每个点模式获取估计的空间强度。我们可以通过简单的代码为超框架中的每一行生成一个额外的列。为了这篇文章,我们只针对星形胶质细胞进行操作:

PointPatterns$AstrocytesIntensity <- with(PointPatterns, summary(Astrocytes)$intensity)

head(PointPatterns[,9:11])

你可以看到我们创建了一个新列,其中包含了星形胶质细胞的空间强度。接下来,我们将这些信息与分组变量一起提取到数据框中:

Astrocytes_df <- as.data.frame(PointPatterns[,9:11])

# We make sure to organize our factor variable in the right order
Astrocytes_df$DPI <- factor(Astrocytes_df$DPI, levels = c("0D", "5D", "15D", "30D"))

gt::gt(Astrocytes_df[1:10,])

这是一个不错的开始,你能够以可重复的方式使用无偏/自动细胞计数来获得单位面积内的细胞数量。让我们从这些数据中做一个简单的科学推断。

拟合一个统计模型来分析空间强度

和往常一样,在我的博客文章中,我们使用brms (6)拟合一个贝叶斯线性模型,在其中我们研究星形胶质细胞的空间强度,并根据 DPI(即该数据集中的动物脑损伤后的天数)进行条件化。我们将建立一个异方差模型(预测 sigma),因为(我知道)不同 DPI 之间的方差是不相等的。在 0D 时,它的方差要小得多。

Astrocytes_Mdl <- bf(AstrocytesIntensity ~ DPI, 
                     sigma ~ DPI)

Astrocytes_Fit <- brm(formula = Astrocytes_Mdl,
                      family = student,
                      data = Astrocytes_df, 
                      # seed for reproducibility purposes
                      seed = 8807,
                      control = list(adapt_delta = 0.99),
                      # this is to save the model in my laptop
                      file    = "Models/2024-05-24_PPA/Astrocytes_Fit.rds",
                      file_refit = "never")

# Add loo for model comparison
Astrocytes_Fit <- 
  add_criterion(Astrocytes_Fit, c("loo", "waic", "bayes_R2"))

让我们看一下模型的摘要表:

我们看到,0D(截距)时动物的平均空间强度为 4.3,95%的可信区间(CI)在 0.73 到 2.90 之间。那是非常少的细胞数量。另一方面,在 15D 时,我们看到一个峰值,平均空间强度为 26.9,可信区间在 22 到 31 之间。

让我们使用强大的TidyBayes包来绘制结果(7),该包由伟大的Matthew Kay开发。

Astrocytes_df %>%
  data_grid(Astrocytes_df) %>%
  add_epred_draws(Astrocytes_Fit) %>%
  ggplot(aes(x = .epred, y = DPI)) +
  labs(x = "Spatial intensity") +
  stat_halfeye() +
  geom_vline(xintercept = 0) +
  Plot_theme

图 4:星形胶质细胞空间强度的后验分布。

图 4 中的stat_halfeye()是查看结果的一个很好的方法。这个过程类似于在给定区域内计数细胞。PPA 的优点在于,你不必依赖学生计数细胞时的假设视力(所谓的专家不是计数的人),而是可以生成无偏的细胞计数,这些计数是可验证的,并且是可重复和可重用的。显然,亲爱的读者,我们可以通过 PPA 做得更多。

创建密度核

我们在加载的点模式中有可用的密度核,但我们将为这篇文章重新编写它们。密度核是一种估计变量概率密度函数的方法,在这种情况下,就是细胞位置。这提供了一个平滑的估计,用于描述生成观测数据的强度函数。

点模式的核密度估计可以表述如下:

我们将使用spatstat中的density函数重新创建星形胶质细胞和小胶质细胞的密度核。请确保此函数未被其他包覆盖。我发现 sigma(带宽)为 0.2 时,点模式的密度读取较为合理。

PointPatterns$Astrocytes_Dens <- with(PointPatterns, density(Astrocytes, sigma = 0.2, col = topo.colors))

在准备好这一切之后,我想给你举一个使用单一大脑示例来说明 sigma 在密度核中的影响:

par(mfrow = c(1,3), mar=c(1,1,1,1), oma=c(1,1,1,1))

plot(density(PointPatterns$Astrocytes$M05, sigma = 0.02), col = topo.colors, main = "sigma = 0.02")
plot(density(PointPatterns$Astrocytes$M05, sigma = 0.2), col = topo.colors, main = "sigma = 0.2")
plot(density(PointPatterns$Astrocytes$M05, sigma = 2), col = topo.colors, main = "sigma = 2")

图 5:具有不同 sigma 的密度核。

图 5 显示,在第一个案例中,我们看到一个非常低的 sigma 会映射单个点。对于 sigma = 0.2,我们看到在更大尺度上的映射,并且可以更好地区分星形胶质细胞的低密度和高密度区域。最后,sigma = 2 提供了一种视角,在这种情况下,我们实际上无法精确地区分星形胶质细胞的不同密度。对于这种情况,sigma = 0.2 是一个很好的折衷选择。

现在我们将拟合一个简单的点过程模型,以研究基于星形胶质细胞密度(通过密度核映射)条件下神经元的相对分布。

拟合点过程模型(ppm)

在这里,我们使用spatstat中的mppm函数来为超框架中的点模式拟合多点过程模型。不幸的是,spatstat中没有类似贝叶斯的方法来处理多点模式。

# We fit the mppm model
Neurons_ppm <- mppm(Neurons ~ Astrocytes_Dens, data = PointPatterns)

# We check the results
summary(Neurons_ppm)

请记住,空间模型是通过使用对数链接函数的泊松分布来拟合的,以获得仅为正的结果。这意味着我们需要对表中的结果进行指数化,以将它们转换回原始尺度。因此,我们可以看到,在基线时神经元的空间强度(当星形胶质细胞密度为 0 时)为 exp(3.54) = 34.4。这种强度会随着星形胶质细胞空间强度(由密度核定义)每单位增加而减少,减少量为 ex(-0.002171358)=-0.99。换句话说,这个模型告诉我们,在星形胶质细胞更多的地方,神经元会更少。请注意,我们没有在回归中包括 DPI,这是您可以进行的一个练习,以查看此估算是否会随着 DPI 的变化而改变。

PPA 还有更多方面可以探索。然而,为了避免让这篇文章过长且繁重,我将在接下来的两篇文章中介绍这些内容。在这里,您可以学习如何计算和提取细胞的空间强度,创建密度核,并使用它们构建点过程模型。在下一篇文章中,我们将探索如何进行相对分布的计算以及如何使用栅格图层进一步探索细胞分布。

我非常感谢您的评论或反馈,告诉我这次旅程是否对您有用。如果您想获得更多关于数据科学和其他话题的优质内容,您可以考虑成为Medium 会员

您可以在我的GitHub 网站上找到这篇文章的完整/更新版本。

  • 所有图像,除非另有说明,均由作者使用 R 代码生成。

参考文献

1.A. Baddeley, E. Rubak, R. Turner,空间点模式:使用 R 的方法论和应用(Chapman; Hall/CRC Press, London, 2015;www.routledge.com/Spatial-Point-Patterns-Methodology-and-Applications-with-R/Baddeley-Rubak-Turner/p/book/9781482210200/)。

2. D. Manrique-Castano, A. ElAli,无偏量化小鼠细胞空间分布的点模式分析STAR Protocols5,102989(2024 年)。

3. D. Manrique-Castano, D. Bhaskar, A. ElAli,《通过空间点模式和拓扑数据分析解剖胶质疤痕形成》(2023),(可在dx.doi.org/10.1101/2023.10.04.560910获取)。

4. P. Bankhead, M. B. Loughrey, J. A. Fernández, Y. Dombrowski, D. G. McArt, P. D. Dunne, S. McQuaid, R. T. Gray, L. J. Murray, H. G. Coleman, J. A. James, M. Salto-Tellez, P. W. Hamilton, QuPath:数字病理图像分析的开源软件。Scientific Reports7(2017),doi:10.1038/s41598–017–17204–5

5. D. R. Stirling, M. J. Swain-Bowden, A. M. Lucas, A. E. Carpenter, B. A. Cimini, A. Goodman, CellProfiler 4: 在速度、功能性和可用性方面的改进。BMC Bioinformatics22 (2021),doi:10.1186/s12859–021–04344–9

6. P.-C. Bürkner, Brms:一个用于贝叶斯多层次模型的 R 包,使用 Stan。80 (2017),doi:10.18637/jss.v080.i01

7. M. Kay, tidybayes: 用于贝叶斯模型的整洁数据和几何图形(2023;mjskay.github.io/tidybayes/)。

支持向量机简介——动机与基础

原文:towardsdatascience.com/introduction-to-support-vector-machines-motivation-and-basics-920e4c1e22e0?source=collection_archive---------5-----------------------#2024-08-10

学习使支持向量机成为强大线性分类器的基本概念

Rishabh MisraTowards Data Science Rishabh Misra

·发表于 Towards Data Science ·阅读时长:8 分钟·2024 年 8 月 10 日

--

支持向量机的分类技术在实际中的应用

简介

在这篇文章中,你将学习支持向量机(SVM)的基础知识,SVM 是一种备受推崇的监督式机器学习算法。

这项技术应该是每个人工具包中的一部分,尤其是那些有志成为数据科学家的人。

因为有很多内容要学习,所以我将通过两篇文章向你介绍支持向量机,这样你可以在两篇文章之间休息一下,喝杯咖啡 😃

动机

首先,让我们尝试在二分类问题的背景下理解支持向量机的动机。在二分类问题中,我们的数据属于两个类别,我们试图找到一个决策边界,将数据分成这两个类别,同时尽量减少错误。请看下面的图示,它代表了我们(假设的)数据在二维平面上的分布。如我们所见,数据被分成了两类:加号和星号。

注:为了简单起见,我们目前只考虑线性可分的数据,并将学习非线性可分的数据……

TensorFlow 的功能性 API 介绍

原文:towardsdatascience.com/introduction-to-tensorflows-functional-api-b0c489da3d52?source=collection_archive---------4-----------------------#2024-12-18

学习功能性 API 是什么,以及如何使用它构建复杂的 Keras 模型

Javier Martínez OjedaTowards Data Science Javier Martínez Ojeda

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

--

图片来自Hunter HarrittUnsplash

TensorFlow顺序 API帮助用户将各层依次堆叠,轻松创建线性模型,其中每一层的输入始终是上一层的输出。但当你的模型需要处理多个输入或输出、共享层或非线性连接时,会发生什么呢?在这些场景中,TensorFlow 的功能性 API可以构建更高级、更灵活、更可定制的模型,使用户能够轻松设计上述复杂架构。

本文将解释:

  • 什么是功能性 API

  • 如何使用功能性 API 构建一个简单模型

什么是 TensorFlow 的功能性 API?

TensorFlow 的功能性 API是一种创建模型的方式,其中各层像网络图一样连接,而不仅仅是像顺序 API那样线性堆叠。可以想象使用乐高积木:顺序 API 只允许我们将积木块一个个堆叠,而功能性 API 则允许我们构建桥梁、塔楼和连接不同区域的小径。

使用 R 进行回归的有限正态混合模型介绍

原文:towardsdatascience.com/introduction-to-the-finite-normal-mixtures-in-regression-with-6a884810a692?source=collection_archive---------7-----------------------#2024-11-15

如何使线性回归足够灵活以处理非线性数据

Lukasz GatarekTowards Data Science Lukasz Gatarek

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

--

线性回归通常被认为不够灵活,无法处理非线性数据。从理论角度来看,它无法应对这些数据。然而,通过在回归模型中使用有限正态混合模型,我们可以使其适用于任何数据集。这样,它就成为一个非常强大的机器学习工具,可以应用于几乎任何数据集,甚至是高度非正态且变量间具有非线性依赖的数据集。

这种方法特别有趣的地方在于其可解释性。尽管具有极高的灵活性,所有检测到的关系都可以直接解释。该模型与神经网络一样具有普适性,但它并不会变成一个黑盒子。你可以读取这些关系,并理解各个变量的影响。

在这篇文章中,我们展示了如何使用马尔可夫链蒙特卡洛(MCMC)采样来模拟回归的有限混合模型。我们将生成具有多个成分(组)的数据,并拟合一个混合模型,通过贝叶斯推断来恢复这些成分。这个过程涉及回归模型和混合模型,并结合 MCMC 技术进行参数估计。

数据模拟为三个线性回归的混合

加载所需的库

我们首先加载必要的库,以便处理回归模型、MCMC 和多元分布。

# Loading the required libraries for various functions
library("pscl")         # For pscl specific functions, like regression models
library("MCMCpack")     # For MCMC sampling functions, including posterior distributions
library(mvtnorm)        # For multivariate normal distribution functio
  • pscl:用于各种统计功能,如回归模型。

  • MCMCpack:包含用于贝叶斯推断的函数,特别是 MCMC 采样。

  • mvtnorm:提供了处理多变量正态分布的工具。

数据生成

我们模拟一个数据集,其中每个观测值属于多个组之一(混合模型的组分),响应变量是使用回归模型生成的,并且具有随机系数。

我们考虑一个使用 G 个正态混合组件的回归模型的通用设置。

## Generate the observations
# Set the length of the time series (number of observations per group)
N <- 1000
# Set the number of simulations (iterations of the MCMC process)
nSim <- 200
# Set the number of components in the mixture model (G is the number of groups)
G <- 3
  • N:每组的观测数。

  • nSim:MCMC 迭代次数。

  • G:混合模型中组件(组)的数量。

数据模拟

每个组都使用单变量回归模型进行建模,其中解释变量(X)和响应变量(y)是从正态分布中模拟出来的。betas表示每个组的回归系数,sigmas表示每个组的方差。

# Set the values for the regression coefficients (betas) for each group
betas <- 1:sum(dimG) * 2.5  # Generating sequential betas with a multiplier of 2.5
# Define the variance (sigma) for each component (group) in the mixture
sigmas <- rep(1, G) / 1  # Set variance to 1 for each component, with a fixed divisor of 1
  • betas:这些是回归系数。每个组的系数会依次分配。

  • sigmas:表示混合模型中每个组的方差。

在这个模型中,我们允许每个混合组分拥有自己的方差参数和回归参数集。

组分配和混合

然后我们使用随机分配模拟每个观测值的组分配,并将所有组件的数据混合在一起。

我们通过一组组件标签向量来扩展模型,以表示

其中

因此,z_gi=1 表示第i个个体来自混合模型的第g个组分。

这种随机分配形成了z_original向量,表示每个观测值所属的真实组。

# Initialize the original group assignments (z_original)
z_original <- matrix(NA, N * G, 1)
# Repeat each group label N times (assign labels to each observation per group)
z_original <- rep(1:G, rep(N, G))
# Resample the data rows by random order
sampled_order <- sample(nrow(data))
# Apply the resampled order to the data
data <- data[sampled_order,]

贝叶斯推断:先验分布与初始化

我们为回归系数和方差设置了先验分布。这些先验将指导我们的贝叶斯估计。

## Define Priors for Bayesian estimation# Define the prior mean (muBeta) for the regression coefficients
muBeta <- matrix(0, G, 1)# Define the prior variance (VBeta) for the regression coefficients
VBeta <- 100 * diag(G)  # Large variance (100) as a prior for the beta coefficients# Prior for the sigma parameters (variance of each component)
ag <- 3  # Shape parameter
bg <- 1/2  # Rate parameter for the prior on sigma
shSigma <- ag
raSigma <- bg^(-1)
  • muBeta:回归系数的先验均值。我们将其设置为所有组的 0。

  • VBeta:先验方差,值较大(100),以允许回归系数有较大的灵活性。

  • shSigmaraSigma:每个组的方差(sigma)的先验分布的形状和速率参数。

对于组件指示符和组件概率,我们考虑以下先验分配。

多项式先验 M 是二项分布的多变量推广,而 Dirichlet 先验 D 是贝塔分布的多变量推广。

MCMC 初始化

在本节中,我们通过设置矩阵来初始化 MCMC 过程,以存储回归系数、方差和混合比例的样本。

## Initialize MCMC sampling# Initialize matrix to store the samples for beta
mBeta <- matrix(NA, nSim, G)# Assign the first value of beta using a random normal distribution
for (g in 1:G) {
  mBeta[1, g] <- rnorm(1, muBeta[g, 1], VBeta[g, g])
}# Initialize the sigma² values (variance for each component)
mSigma2 <- matrix(NA, nSim, G)
mSigma2[1, ] <- rigamma(1, shSigma, raSigma)# Initialize the mixing proportions (pi), using a Dirichlet distribution
mPi <- matrix(NA, nSim, G)
alphaPrior <- rep(N/G, G)  # Prior for the mixing proportions, uniform across groups
mPi[1, ] <- rdirichlet(1, alphaPrior)
  • mBeta:用于存储回归系数样本的矩阵。

  • mSigma2:用于存储每个组方差(sigma 平方)的矩阵。

  • mPi:用于存储混合比例的矩阵,通过 Dirichlet 分布初始化。

MCMC 采样:后验更新

如果我们在组件指示变量 z 的值上进行条件化,则条件似然可以表示为

在 MCMC 采样循环中,我们基于后验分布更新组分配(z)、回归系数(beta)和方差(sigma)。计算每个组分配的似然,并选择具有最高后验概率的组。

以下完整的后验条件可以得到:

其中

表示后验中的所有参数,除了x

其中,n_g 表示混合模型中第 g 个组的观测数。

以下算法按照顺序从上面的后验分布系列中提取样本。

## Start the MCMC iterations for posterior sampling# Loop over the number of simulations
for (i in 2:nSim) {
  print(i)  # Print the current iteration number

  # For each observation, update the group assignment (z)
  for (t in 1:(N*G)) {
    fig <- NULL
    for (g in 1:G) {
      # Calculate the likelihood of each group and the corresponding posterior probability
      fig[g] <- dnorm(y[t, 1], X[t, ] %*% mBeta[i-1, g], sqrt(mSigma2[i-1, g])) * mPi[i-1, g]
    }
    # Avoid zero likelihood and adjust it
    if (all(fig) == 0) {
      fig <- fig + 1/G
    }

    # Sample a new group assignment based on the posterior probabilities
    z[i, t] <- which(rmultinom(1, 1, fig/sum(fig)) == 1)
  }

  # Update the regression coefficients for each group
  for (g in 1:G) {
    # Compute the posterior mean and variance for beta (using the data for group g)
    DBeta <- solve(t(X[z[i, ] == g, ]) %*% X[z[i, ] == g, ] / mSigma2[i-1, g] + solve(VBeta[g, g]))
    dBeta <- t(X[z[i, ] == g, ]) %*% y[z[i, ] == g, 1] / mSigma2[i-1, g] + solve(VBeta[g, g]) %*% muBeta[g, 1]

    # Sample a new value for beta from the multivariate normal distribution
    mBeta[i, g] <- rmvnorm(1, DBeta %*% dBeta, DBeta)

    # Update the number of observations in group g
    ng[i, g] <- sum(z[i, ] == g)

    # Update the variance (sigma²) for each group
    mSigma2[i, g] <- rigamma(1, ng[i, g]/2 + shSigma, raSigma + 1/2 * sum((y[z[i, ] == g, 1] - (X[z[i, ] == g, ] * mBeta[i, g]))²))
  }

  # Reorder the group labels to maintain consistency
  reorderWay <- order(mBeta[i, ])
  mBeta[i, ] <- mBeta[i, reorderWay]
  ng[i, ] <- ng[i, reorderWay]
  mSigma2[i, ] <- mSigma2[i, reorderWay]

  # Update the mixing proportions (pi) based on the number of observations in each group
  mPi[i, ] <- rdirichlet(1, alphaPrior + ng[i, ])
}

这段代码执行了 MCMC 中的关键步骤:

  • 组分配更新:对于每个观测值,我们计算数据属于每个组的似然,并相应地更新组分配。

  • 回归系数更新:每个组的回归系数使用后验均值和方差进行更新,这些值是根据观察到的数据计算得出的。

  • 方差更新:每个组的响应变量方差使用逆伽马分布进行更新。

结果可视化

最后,我们可视化 MCMC 采样的结果。我们绘制每个回归系数的后验分布,将其与真实值进行比较,并绘制最可能的组分配。

# Plot the posterior distributions for each beta coefficient
par(mfrow=c(G,1))
for (g in 1:G) {
  plot(density(mBeta[5:nSim, g]), main = 'True parameter (vertical) and the distribution of the samples')  # Plot the density for the beta estimates
  abline(v = betas[g])  # Add a vertical line at the true value of beta for comparison
}

该图显示了 MCMC 样本(后验分布)如何收敛到回归系数的真实值(betas)。

结论

通过这个过程,我们展示了如何在回归上下文中使用有限的正态混合模型,并结合 MCMC 进行参数估计。通过模拟具有已知分组的数据,并通过贝叶斯推断恢复参数,我们可以评估我们的模型在多大程度上捕捉到了数据的潜在结构。

除非另有说明,所有图像均为作者提供。

JavaScript 中 async/await 的直观解释

原文:towardsdatascience.com/intuitive-explanation-of-async-await-in-javascript-730174c000bd?source=collection_archive---------1-----------------------#2024-09-08

设计高效数据处理的异步管道

Vyacheslav EfimovTowards Data Science Vyacheslav Efimov

·发布于 Towards Data Science ·阅读时长 10 分钟·2024 年 9 月 8 日

--

注意。 本文假设你已经熟悉回调函数、Promise,并且对 JavaScript 中的异步编程模式有基本的理解。

引言

异步机制是 JavaScript 中最重要的概念之一,也是编程中的关键概念。它允许程序在后台独立执行次要任务,而不阻塞当前线程执行主要任务。当次要任务完成时,其结果被返回,程序继续正常运行。在这个背景下,这些次要任务被称为异步任务。

异步任务通常包括向外部环境发送请求,比如数据库、Web API 或操作系统。如果异步操作的结果不影响主程序的逻辑,那么与其只是等待任务完成,不如充分利用这段时间,继续执行主要任务。

然而,有时候异步操作的结果会立即在后续代码行中使用。在这种情况下,后续的代码行应该在异步操作完成之前不执行…

直观的时间序列数据框过滤

原文:towardsdatascience.com/intuitive-temporal-dataframe-filtration-fa9d5da734b3?source=collection_archive---------8-----------------------#2024-05-27

摆脱你那无效的时间序列数据过滤代码

Yousef NamiTowards Data Science Yousef Nami

·发表于 Towards Data Science ·阅读时间 10 分钟·2024 年 5 月 27 日

--

图片来源:Carlos Muza 供职于Unsplash

每次我处理时间序列数据时,我都会写出复杂且不可重用的代码来进行过滤。无论是进行简单的过滤,如移除周末,还是更复杂的操作,如移除特定的时间段,我总是依赖编写一个简便且不太优雅的函数,这个函数仅适用于当前要过滤的内容,但再也不会使用。

我最终决定通过编写一个处理器来打破这个恶性循环,该处理器可以让我用非常简单且简洁的输入过滤时间序列,无论条件多么复杂。

下面是一个它在实践中如何工作的示例:

  • 在工作日,我想移除早于 6 点和晚于 8 点的时间段,而在周末,我想移除早于 8 点和晚于 10 点的时间段。
 df = pl.DataFrame(
   {"date": [
      # -- may 24th is a Friday, weekday
      '2024-05-24 00:00:00',  # < 6 am, should remove
      '2024-05-24 06:00:00',  # not < 6 am, should keep
      '2024-05-24 06:30:00',  # not < 6 am, should keep
      '2024-05-24 20:00:00',  # >= 8 pm, should remove

      # -- may 25th is a Saturday, weekend
      '2024-05-25 00:00:00',  # < 8 am, should remove
      '2024-05-25 06:00:00',  # < 8 am, should remove
      '2024-05-25 06:30:00',  # < 8 am, should remove
      '2024-05-25 20:00:00',  # not >= 10 pm, should keep

      ]
   }
).with_columns(pl.col("date").str.strptime(pl.Datetime, "%Y-%m-%d %H:%M:%S"))
  • 没有处理器:表达性强,但冗长且不可重用
df.filter(   
    pl.Expr.not_(
        (
            (pl.col("date").dt.weekday() < 6)
            .and_(
                (pl.col("date").dt.hour() < 6)
                .or_(pl.col("date").dt.hour() >= 20)
            )
        )
        .or_(
            (pl.col("date").dt.weekday() >= 6)
            .and_(
                (pl.col("date").dt.hour() < 8)
                .or_(pl.col("date").dt.hour() >= 22)
            )
        )
    )
)
  • 有处理器:同样具有表达性,简洁且可重用
processor = FilterDataBasedOnTime(
    "date", time_patterns=[
        "<6wd<6h",
        "<6wd>=20h",
        ">=6wd<8h",
        ">=6wd>=22h",
    ]
)
processor.transform(df)

在本文中,我将解释我是如何想出这个解决方案的,首先从我选择的字符串格式开始,用于定义过滤条件,然后设计处理器本身。在文章的最后部分,我将描述如何将这个处理流程与其他流程一起使用,以便只通过几行代码实现复杂的时间序列处理。

如果你只对代码感兴趣,可以跳到文章末尾获取链接,查看代码库。

富有表现力、简洁且灵活的时间条件?

这是这个任务中最难的部分。基于时间过滤时间序列在概念上很容易,但用代码实现却要困难得多。我的最初想法是使用一个对我自己来说最直观的字符串模式:

# -- remove values between 6 am (inclusive) and 2 pm (exclusive)
pattern = '>=06:00,<14:00'

然而,使用这种方法,我们立即遇到了一个问题:我们失去了灵活性。这是因为06:00是模糊的,它可能表示分钟:秒小时:分钟。因此,我们几乎总是必须事先定义日期格式。

这使得我们无法实现复杂的过滤技术,例如在特定的日期过滤特定的时间(例如,只在周六的[6am, 2pm)之间移除值)。

将我的模式扩展为类似cron的形式也没有帮助:

# cronlike pattern
pattern = ‘>=X-X-X 06:00:X, <X-X-X 20:00:X’

上述方法可以帮助选择特定的月份或年份,但无法提供像星期几这样的灵活性。此外,使用所有的 X 符号使得它不太具有表现力,而且非常冗长。

我知道我需要一种允许链式连接单独时间序列组件或单位的模式。实际上,这种模式就像一个 if 语句:

  • 如果日期 == 星期六

  • 且时间 ≥ 06:00

  • 且时间 < 14:00

所以我想,为什么不使用一种可以为时间组件添加任何条件的模式,并隐含假设它们都是 AND 条件呢?

# -- remove values in [6am, 2pm) on Saturday
pattern = 'day==6,time>=06:00,time<14:00'

现在我们有了一个富有表现力的模式,但它仍然可能存在歧义,因为time隐含假设了日期格式。因此,我决定进一步深入:

# -- remove values in [6am, 2pm) on Saturday
pattern = 'day==6,hour>=6,hour<14'

现在为了减少冗余,我借用了 Polars 的持续时间字符串格式(如果你更熟悉 Pandas 的话,这等同于“频率”),于是就有了这个:

# -- remove values in [6am, 2pm) on Saturday
pattern = '==6wd,>=6h,<14h'

那么,关于需要 OR 运算符的时间条件呢?

让我们考虑一个不同的条件:过滤所有小于 6 am(包含)且大于 2 pm(不包含)的时间。如下的模式会失败:

# -- remove values in (-inf, 6am], and (2pm, inf)
pattern = '<=6h,>14h'

因为我们会将其读作:≤ 6 am 且 > 2 pm

没有满足这两个条件的值存在!

但解决方案很简单:在一个模式内应用 AND 条件,在不同模式之间应用 OR 条件。所以:

# -- remove values in (-inf, 6am], and (2pm, inf)
patterns = ['<=6h', '>14h']

这将被解读为:≤ 6 am 或 > 2 pm

为什么不允许在模式内使用 OR 语句呢?

我确实考虑过在模式内添加支持 OR 语句,例如使用|,或者让,表示“左”条件和“右”条件之间的区别。然而,我发现这些会增加解析模式的复杂性,却并没有使代码更加简洁。

我更喜欢简单:在一个模式内我们应用 AND,在不同模式之间我们应用 OR。

边缘案例

这里有一个值得讨论的边缘案例。类似“if 语句”的模式并不总是有效。

让我们考虑过滤大于06:00的时间戳。如果我们仅定义:

# -- pattern to remove values > 06:00
pattern = '>6h'

那么,我们是否应该这样解读:

  • 移除所有hour>6的值

  • 或者移除所有time>06:00的值?

后者更有意义,但当前的模式无法表达这一点。因此,为了明确说明我们希望包括大于当天第六个小时的时间戳,我们必须添加我所称的级联操作符:

# -- pattern to remove values > 06:00
pattern = '>6h*'

它将被解读为:

  • hour > 6

  • OR (hour == 6 AND any(minute, second, millisecond, etc… > 0)

这将是一个准确的条件,用来捕获时间>06:00!

代码

在这里,我突出了设计中的重要部分,用于创建一个处理器来过滤时间序列数据。

解析逻辑

由于模式非常简单,解析它其实非常容易。我们需要做的就是遍历每个模式,并跟踪操作符字符。剩下的就是操作符列表和它们应用的持续时间列表。

# -- code for parsing a time pattern, e.g. "==7d<7h"
pattern = pattern.replace(" ", "")
operator = ""
operators = []
duration_string = ""
duration_strings = []
for char in pattern:
    if char in {">", "<", "=", "!"}:
        operator += char
        if duration_string:
            duration_strings.append(duration_string)
            duration_string = ""
    else:
        duration_string += char
        if operator:
            operators.append(operator)
            operator = ""
duration_strings.append(duration_string)

现在,对于每个操作符和持续时间字符串,我们可以提取有助于我们稍后制定实际布尔规则的元数据。

# -- code for extracting metadata from a parsed pattern

# -- mapping to convert each operator to the Polars method
OPERATOR_TO_POLARS_METHOD_MAPPING = {
    "==": "eq",
    "!=": "ne",
    "<=": "le",
    "<": "lt",
    ">": "gt",
    ">=": "ge",
}

operator_method = (
    OPERATOR_TO_POLARS_METHOD_MAPPING[operator]
)

# -- identify cascade operations
if duration_string.endswith("*"):
    duration_string = duration_string[:-1]
    how = "cascade"
else:
    how = "simple"

# -- extract a polars duration, e.g. 7d7h into it's components: [(7, "d"), (7, "h")]
polars_duration = PolarsDuration(duration=duration_string)
decomposed_duration = polars_duration.decomposed_duration

# -- ensure that cascade operator only applied to durations that accept it
if how == "cascade" and any(
    unit not in POLARS_DURATIONS_TO_IMMEDIATE_CHILD_MAPPING
    for _, unit in decomposed_duration
):
    raise ValueError(
        (
            "You requested a cascade condition on an invalid "
            "duration. Durations supporting cascade: "
            f"{list(POLARS_DURATIONS_TO_IMMEDIATE_CHILD_MAPPING.keys())}"
        )
    )

rule_metadata = {
    "operator": operator_method,
    "decomposed_duration": decomposed_duration,
    "how": how,
}

现在,对于每个模式,我们都有了字典来定义它的每个组件的规则。因此,如果我们选择一个复杂的例子:

pattern = '==1m>6d6h' # remove if month = Jan, and day > 6 and hour > 6

# parsed pattern

[
   [
      {
         "operator": "eq",
         "decomposed_duration": [(1, "m")],
         "how": "simple"
      },
      { 
         "operator": "gt",
         "decomposed_duration": [(6, "d"), (6, "h")],
         "how": "simple"
      }
   ]
]

注意,一个模式可以被拆分成多个元数据字典,因为它可以由多个持续时间和操作组成。

从元数据创建规则

创建了每个模式的元数据后,现在进入有趣的部分——创建 Polars 规则!

请记住,在每个模式内部,我们应用的是 AND 条件,但在不同模式之间,我们应用的是 OR 条件。所以在最简单的情况下,我们需要一个包装器,它可以接受特定模式的所有元数据列表,然后对其应用 AND 条件。我们可以将这个表达式存储在一个列表中,和所有其他模式的表达式一起,之后再应用 OR 条件。

# -- dictionary to contain each unit along with the polars method to extract it's value
UNIT_TO_POLARS_METHOD_MAPPING = {
        "d": "day",
        "h": "hour",
        "m": "minute",
        "s": "second",
        "ms": "millisecond",
        "us": "microsecond",
        "ns": "nanosecond",
        "wd": "weekday",
}

patterns = ["==6d<6h6s"]
patterns_metadata = get_rule_metadata_from_patterns(patterns)

# -- create an expression for the rule pattern
pattern_metadata = patterns_metadata[0]  # list of length two

# -- let's consider the condition for ==6d
condition = pattern_metadata[0]

decomposed_duration = condition["decomposed_duration"]  # [(6, 'd')]
operator = condition["operator"]  # eq
conditions = [
    getattr(  # apply the operator method, e.g. pl.col("date").dt.hour().eq(value)
        getattr(  # get the value of the unit, e.g. pl.col("date").dt.hour()
            pl.col(time_column).dt,  
            UNIT_TO_POLARS_METHOD_MAPPING[unit], 
        )(),
        operator,
    )(value) for value, unit in decomposed_duration  # for each unit separately
]

# -- finally, we aggregate the separate conditions using an AND condition
final_expression = conditions.pop()
for expression in conditions:
    final_expression = getattr(final_expression, 'and_')(expression)

这看起来很复杂……但是我们可以将其中的一部分转换为函数,最终的代码看起来非常简洁易读:

rules = []  # list to store expressions for each time pattern
for rule_metadata in patterns_metadata:
    rule_expressions = []
    for condition in rule_metadata:
        how = condition["how"]
        decomposed_duration = condition["decomposed_duration"]
        operator = condition["operator"]
        if how == "simple":
            expression = generate_polars_condition(  # function to do the final combination of expressions
                [
                    self._generate_simple_condition(
                        unit, value, operator
                    )  # this is the complex "getattr" code
                    for value, unit in decomposed_duration
                ],
                "and_",
            )
        rule_expressions.append(expression)

    rule_expression = generate_polars_condition(
        rule_expressions, "and_"
    )
    rules.append(rule_expression)

overall_rule_expression = generate_polars_condition(
    rules, "or_"
).not_()  # we must negate because we're filtering!

为级联操作符创建规则

在上面的代码中,我只为“简单”条件设置了 if 条件……那么我们该如何处理级联条件呢?

请记住,在我们上面的讨论中,“>6h*”的模式意味着:

hour > 6 OR (hour == 6 AND any(min, s, ms, etc… > 0)

所以我们需要做的,是了解每个单位对应的后续较小单位。

例如,如果我有">6d*",我应该知道在我的任何条件中包括“小时”,因此:

day > 6 OR (day == 6 AND any(hr, min, s, ms, etc… > 0)

这可以通过使用一个字典来轻松实现,该字典将每个单位映射到它的“下一个”较小单位。例如:day → hour,hour → second,等等……

POLARS_DURATIONS_TO_IMMEDIATE_CHILD_MAPPING = {
    "y": {"next": "mo", "start": 1},
    "mo": {"next": "d", "start": 1},
    "d": {"next": "h", "start": 0},
    "wd": {"next": "h", "start": 0},
    "h": {"next": "m", "start": 0},
    "m": {"next": "s", "start": 0},
    "s": {"next": "ms", "start": 0},
    "ms": {"next": "us", "start": 0},
    "us": {"next": "ns", "start": 0},
}

启动值是必要的,因为任何条件不一定总是 > 0。因为如果我想过滤掉所有大于二月的值,那么 2023-02-02 应该包含在内,而 2023-02-01 则不应该。

有了这个字典,我们就可以轻松创建任何条件:

# -- pattern example: >6h* cascade
simple_condition = self._generate_simple_condition(
    unit, value, operator
)  # generate the simple condition, e.g. hour>6
all_conditions = [simple_condition]
if operator == "gt":  # cascade only affects > operator
    equality_condition = self._generate_simple_condition(
        unit, value, "eq"
    )  # generate hour==6
    child_unit_conditions = []
    child_unit_metadata = (
        POLARS_DURATIONS_TO_IMMEDIATE_CHILD_MAPPING.get(unit, None)
    )  # get the next smallest unit, e.g. minute
    while child_unit_metadata is not None:
        start_value = child_unit_metadata["start"]
        child_unit = child_unit_metadata["next"]
        child_unit_condition = self._generate_simple_condition(
            child_unit, start_value, "gt"
        )  # generate minute > 0
        child_unit_conditions.append(child_unit_condition)
        child_unit_metadata = (
            POLARS_DURATIONS_TO_IMMEDIATE_CHILD_MAPPING.get(
                child_unit, None
            )
        )  # now go on to seconds, and so on...

    cascase_condition = generate_polars_condition(
        [
            equality_condition,  # and condition for the hour unit
            generate_polars_condition(child_unit_conditions, "or_"),  # any condition for all the child units
        ],
        "and_",
    )

    all_conditions.append(cascase_condition)

# -- final condition is hour>6 AND the cascade condition
overall_condition = generate_polars_condition(all_conditions, "or_")

更大的图景

像这样的处理器不仅仅对临时分析有用。它可以成为数据处理管道的核心组件。对我来说,一个非常有用的用例是将其与重采样结合使用。一个简单的过滤步骤将使我能够轻松地计算具有规律性中断或规律性停机的时间序列的指标。

此外,通过一些简单的修改,我可以扩展这个处理器,使其能够轻松标记我的时间序列。这使我能够为那些我知道行为不同的部分添加回归变量,例如,如果我正在建模一个在特定时刻跳变的时间序列,我可以只在那些部分添加一个阶跃回归变量。

结语

在本文中,我概述了一个处理器,使得在 Polars 数据集上进行简便、灵活和简洁的时间序列过滤成为可能。所讨论的逻辑可以通过一些小的修改扩展到你最喜欢的数据框处理库,比如 Pandas。

这个处理器不仅对临时的时间序列分析有用,还可以成为数据处理的核心,如果与其他操作(如重采样)链式使用,或者用于为建模创建额外特征时。

我将通过一些我想到的扩展来结束这篇文章,以使代码更加完善:

  • 我正在考虑创建一个快捷方式来定义“周末”,例如,“==we”。这样,我就不需要显式地定义“>=6wd”,这可能不太清晰。

  • 通过适当的设计,我认为可以实现添加自定义时间标识符。例如,“==eve”表示傍晚,时间范围可以由用户定义。

  • 我一定会添加对仅仅标记数据的支持,而不是过滤数据。

  • 我还打算添加支持将边界定义为“keep”,例如,代替定义["<6h", ">=20hr"],我可以使用[">=6h<20hr"]。

如何查找代码

这个项目还处于初期阶段,因此项目内容可能会有所变动。截止 2024 年 5 月 23 日,你可以在mix_n_match/main.py中找到FilterDataBasedOnTime

[## GitHub - namiyousef/mix-n-match: 数据框处理的代码库

数据框处理的代码库。通过在 GitHub 上创建帐户,参与 namiyousef/mix-n-match 开发…

github.com](https://github.com/namiyousef/mix-n-match?source=post_page-----fa9d5da734b3--------------------------------)

除非另有说明,所有代码、数据和图片均为作者所有

观点推动创新,而非数据,iPhone 创造者如是说

原文:towardsdatascience.com/iphone-creator-suggests-opinions-drive-innovation-not-data-bddb73ef62ff?source=collection_archive---------8-----------------------#2024-02-27

摆脱数据陷阱:如何通过观点解锁真正的创新

Daniel KangTowards Data Science Daniel Kang

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

--

来源:DALL-E

我们需要以数据为驱动,”每个人都这么说。没错,我 90%的时候同意这一点,但它不应该被当作一个一概而论的命题。像生活中的其他一切一样,认识到它适用和不适用的地方非常重要。

在一个痴迷数据的世界里,正是那些大胆且富有主见的决策突破了常规,带来了革命性的创新。

数据落后于创新的潮流

《经济学人》曾报道过关于麦肯锡在智能手机时代初期,1980 年代犯下的严重失误。AT&T 曾要求麦肯锡预测智能手机市场的规模。

麦肯锡在经过严格的预测、专家咨询和数据分析后,分享了他们的估算结果,认为总市场大约会有 90 万个智能手机。他们的预测是基于数据,尤其是当时的数据。那时的手机笨重、体积大,对移动办公人士来说只是一个不得不拥有的工具。数据滞后了。

AT&T 最初撤出了市场,部分原因就是因为这些建议,之后他们又重新回到市场与其他公司竞争。并非所有公司都那么幸运。在韩国的每一位战略咨询师都知道麦肯锡曾向某家公司提供类似的建议……

复杂写作仅仅是公式吗?

原文:towardsdatascience.com/is-complex-writing-nothing-but-formulas-289e0a33793f?source=collection_archive---------2-----------------------#2024-12-13

文本分析揭示了大量写作是如何产生的

Vered ZimmermanTowards Data Science Vered Zimmerman

·发表于Towards Data Science·阅读时长 8 分钟·2024 年 12 月 13 日

--

广义上来说,自然语言处理将语言转化为可以有效操作的结构。由于深度学习的嵌入方法已经证明其强大,它们也成为了默认选择:选择一个模型,嵌入数据,选择一个指标,进行一些 RAG 操作。为了创造新的价值,采用一种不同的方式来处理语言会有帮助。

今天我要分享的故事始于多年前,一本书。

《兰花窃贼》既是非小说类作品,又充满了恶作剧。我第一次读它是在二十多岁时,跳过了大部分历史轶事,只渴望阅读第一人称的叙述。当时,我大笑不止,但也在安静的愤怒中翻页,感叹有人竟能如此深入地生活,又能写得如此出色。我并不确定这两者是否真的是不同的事物。

一年后,我搬到了伦敦,重新开始。

我进入了金融服务行业,那简直像是为书呆子们准备的主题公园。而且,在接下来的十年里,我只会选择那些需要大量写作的工作。

“很多”是关键词。

在现代化的职业服务外表下,英国工业依然活跃于其古老的工厂和船厂。它雇佣 Alice 来做某项工作,然后交给 Bob;他拧动一些螺丝,再交给 Charlie。一月后,我们又开始了同样的工作。作为一个新来者,我发现这里的习惯不那么像一个会让人掉进去的沟渠,更像是一个可以立足的小山丘。

我也在读很多书。好吧,我读的是《纽约客》。我最喜欢的事情就是把一本新杂志翻到封面,倒过来从后面开始阅读其中一篇,作者 Anthony Lane 写的是电影评论。多年来,我从来没有去看过一部电影。

不时地,一个闪光点会让我措手不及。《纽约客》语料库和我那些非普利策奖作品之间的微弱联系。在这两个语料库中,每一篇作品都与它的兄弟姐妹不同,但也……并不完全不同。相似之处回响。而我知道,我的作品中的相似之处是由一个重复过程产生的。

在 2017 年,我开始冥思苦想,探索那条将感觉公式化的写作与能够明确写出公式的写作之间的界限。

这个论点是这样的:重复的量暗示了某种(通常是隐性)算法决策的形式。但是过程性重复会留下指纹。追踪这些指纹就能揭示出过程;推测出算法;软件几乎就能自己写出来。

在我的上一份工作中,我不再写大量文章了。是我的软件在写。

企业理论上可以了解足够多的自身流程,从而获得巨大的收益,但很少有人愿意去做。人们似乎更着迷于别人在做什么。

例如,我的上司,后来我的客户,一直希望他们的员工能够模仿《经济学人》的家风。但你如何找出《经济学人》是如何写出这些文章的?

作者提供的图片

进入文本分析

阅读一篇《经济学人》文章,你会觉得轻松且自信。阅读很多篇后,它们听起来有些相似。一整本印刷杂志每周都会出版一次。是的,我当时是在押注这个过程。

为了好玩,我们来对几百篇《经济学人》文章应用一个可读性函数(以教育年限为单位)。我们也对几百篇由一位沮丧的欧洲资产经理发布的文章进行同样的操作。

然后,我们来做一个直方图,看看这些可读性评分是如何分布的。

仅仅两个函数,看看我们得到的见解!

可读性分析图。来源:FinText

注意曲线的分离程度;这位资产经理的文章并不像《经济学人》。我们可以进一步深入分析,看看是什么导致了这种差异。(首先,通常是疯狂长的句子。)

但同时,注意《经济学人》对它们允许的可读性评分设置了严格的上限。曲线看起来不自然,表明它们在编辑过程中应用了严格的可读性检查。

最后——这是许多我的客户难以应对的——《经济学人》承诺写得足够简明,让一个普通的高中生也能理解。

我曾期待过这些图表。我曾在纸上胡乱画过它们。但当真正的图表第一次出现在我的屏幕上时,就像语言本身笑了出来。

现在,我并不是第一个出现在这个领域的人。早在 1964 年,统计学家 Frederick Mosteller 和 David Wallace 就登上了Time杂志封面,他们的法医文学分析解决了一个长达 140 年的争议,争议的焦点是关于一系列匿名写作的文章作者归属问题。

但法医分析总是将单一项目与两个语料库进行比较:一个是怀疑的作者创作的,另一个是零假设。比较分析只关心比较文本集合。

作者提供的图片

构建文本分析引擎

让我们回顾一下我们的步骤:给定一个语料库,我们对每个文本应用相同的函数(可读性函数)。这将语料库映射到一个集合(在这种情况下是数字)。在这个集合上,我们应用了另一个函数(直方图)。最后,我们对两个不同的语料库做了同样的操作——并比较了结果。

如果你眯起眼睛,你会发现我刚刚描述的是 Excel。

看起来像是一个表格,实际上是一个 管道,依次处理列数据。首先沿列进行处理,然后对结果应用函数,再进行比较分析函数。

好吧,我想要的是 Excel,但它是针对文本的。

不是字符串——是文本。我想应用像Count Verbs(统计动词)或First Paragraph Subject(第一段主题)或First Important Sentence(第一重要句子)这样的函数。而且它必须足够灵活,这样我可以问任何问题;谁知道什么最终会变得重要?

到 2020 年,这种解决方案并不存在,所以我自己构建了它。哇,这个软件可真不是‘自己写出来的’!为了能够问任何问题,需要一些好的架构决策,而我在纠正问题之前错了两次。

最终,函数是一次性定义的,通过它们对单个输入文本所做的处理来定义。然后,你选择和挑选管道步骤,以及它们作用的语料库。

有了这些,我创办了一家写作技术咨询公司,FinText。我打算在为客户服务的同时进行构建,看看什么能成功。

市场的声音

我想到的第一个商业应用案例是社交监听。市场研究和民意调查是一个大生意。那时正值大流行的高峰期,每个人都待在家里。我想到通过处理专门在线社区中的活跃讨论,可能是获取客户思维的一种新方式。

任何第一个软件客户都会感觉特别,但这个客户让我感到兴奋,因为我的创意确实帮助了真实的人走出困境:

在筹备一个大型活动时,他们计划推出一份旗舰报告,报告数据来自一项付费的 YouGov 调查。但调查结果平平。因此,他们用剩余的预算购买了 FinText 研究报告。正是我们的发现被他们放在了最终报告的核心位置。

Reddit‘投资’板块的社交聆听,2020 年。来源:FinText

但社交聆听并没有起飞。投资领域有其特殊性,因为资金池总是需要一个归宿;唯一的问题是,谁是房东。我与业内人士交谈时,大多数人只是想知道他们的竞争对手在做什么。

所以第二个用例——竞争性内容分析——得到了更热烈的反响。我成功说服了大约六家公司采用这一解决方案(例如,Aviva Investors)。

一直以来,我们的引擎都在收集别人没有的数据。凭我的聪明才智,甚至都不是我想出来的举办培训课程的点子,是客户首先提出的需求。通过这个,我学到了公司很喜欢购买培训。

否则,我的蒸汽朋克式写作方式卖起来确实有些棘手。它太抽象了。我需要的是一个仪表盘:漂亮的图表,带有真实的数据,从实时数据中得出结论。一个数据管道负责运算,我雇了一支小团队来做漂亮的图表。

文本分析仪表盘演示。来源:FinText

在仪表盘中,有两张图表显示了话题的细分,其他图表则分析了写作风格。我想稍微谈一谈这个选择。

每个人都相信他们所说的很重要。如果别人不在乎,那其实是道德上的失败,是把风格看得比实质更重要。有点像坏品味只会存在于别人身上。

科学家们已经统计了点击次数、追踪了眼动、监测了滚动、计时了注意力。我们知道,读者需要几分之一秒的时间来决定某个内容是否“适合他们”,而他们的决定是通过将新信息与自己已有的喜好进行模糊比较来做出的。风格是进入的通行证。

仪表盘显示的内容

之前,我并没有跟踪收集的数据,但现在我有了那些漂亮的图表。它们显示了我既是对的,又是非常、非常错的。

最初,我只对少数几家大型投资公司有直接了解,并怀疑它们的竞争对手的流量差不多。这后来证明是对的。

但我也曾假设稍小的公司输出量会少一些。结果这并不成立。

如果一家公司已经具备写作生产能力,文本分析就能发挥作用。否则,他们需要的是一个实际运作的工厂。第一类公司太少了,因为其他公司都涌向了第二类。

后记

作为一项产品,文本分析一直是喜忧参半。它赚了一些钱,可能还能赚更多,但不太可能成为一种爆发性成功。

此外,我已经失去了对《纽约客》的兴趣。某个时候,它的所有内容都变得过于公式化,魔力也随之消失。

现在,文字正处于它们的大规模时代,比如像 ChatGPT 这样的语言模型。早期,我曾考虑应用管道来辨别文本是否由机器生成,但那有什么意义呢?

相反,在 2023 年底,我开始致力于一个解决方案,帮助公司扩展为专家客户写作的能力。这是一次完全不同的冒险,仍处于起步阶段。

最终,我开始把文本分析看作是一副额外的眼镜。偶尔,它能把模糊的东西变得清晰。我把它随身携带,以备不时之需。

Google 的 NotebookLM 会颠覆播客行业吗?

原文:towardsdatascience.com/is-googles-notebooklm-going-to-disrupt-the-podcasting-industry-ea8e1ec7f431?source=collection_archive---------2-----------------------#2024-10-10

生成式 AI

尤其是,如果只需要点击一下,就能将任何内容转化为播客。

Varshita Sher 博士Towards Data Science Varshita Sher 博士

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

--

图片由作者使用 Midjourney 生成

什么是 NotebookLM?

对于那些可能不太了解的人,NotebookLM 是一个个性化的 AI 研究助手,基于 Gemini 1.5 Pro 技术,旨在帮助理解复杂信息。除了根据你上传的资料(文档、幻灯片、图表等)回答问题外,它还可以通过自动生成目录、学习指南、简报、常见问题解答等个性化学习材料。虽然它根据上传的资料生成答案,但也会提供内联引用,突出显示用于生成答案的源文档中的特定文本块。

上传的内容可以包括从研究论文和会议记录到有趣书籍的引用、你正在写的小说章节、公司文档等。这些来源可以是 Google 文档、幻灯片、PDF 文件、文本文件、复制的文本,甚至是网页。

什么是 NotebookLM 的音频概览?

现在,回到这篇文章的核心内容:上个月,NotebookLM 发布了一个新功能——音频概览——该功能引起了广泛关注。这个功能提供了一种与源文档互动的新方式,只需点击一下,就能生成有趣的“深度讨论”,总结源文档中的关键主题。

更令人印象深刻的是,它如何通过生成两位 AI 主持人(一个男性和一个女性),以播客式的格式讨论文档内容,从而将任何内容转化为引人入胜的形式,无论原内容多么枯燥。

如果你在想“播客风格格式”是什么意思,想象一下友好的闲聊、一些笑话、来回的对话、笑声、打断、”嗯“和”你知道“——基本上是所有优秀播客听感的标志。

这些播客风格的对话能够自然地从你的文本中创造出连接和过渡,形成引人入胜的对话。

如何创建 NotebookLM 音频概览

为了进行测试,我决定重新利用我的一篇旧 Medium 文章,并为它创建一个播客,以迎合更多喜爱音频的听众。

设置过程非常简单。

  • 访问NotebookLM。如果你还没有登录,需要使用你的 Google ID 登录。如果是第一次访问,你将看到几个示例笔记本,并且可以通过点击“Create”按钮创建一个新的笔记本。

  • 接下来,向你的笔记本添加内容。我使用网站源将我的 Medium 文章导入。你也可以粘贴文本或从 Google Drive 中提取内容。

  • 最后,点击笔记本指南中的“Generate”按钮(参见下面的图片)来生成音频。然后去拿一杯☕️,因为根据内容的长度,可能需要几分钟时间。

上传的来源:Medium 文章 — “实施生成 AI 产品的挑战”

附言:从我 1100 字的文章生成 13 分钟的音频大约花了 4 分钟时间。你可以在这里播放并聆听。

使用 NotebookLM 生成的播客音频

我的第一印象

附言:我最终尝试了用各种来源进行音频概览,包括播客转录、研究论文和数据科学博客。以下的总结是我在这些来源中的经验汇总。

让我们从好消息开始:

  • 令人惊讶的是,我们可以在几分钟内迅速创建一集播客,这让许多人如果愿意的话可以有副业做播客。这是作家重新利用他们的内容的一个绝佳方式,也让其他人可以以一种有趣且易于接触的方式参与到相对复杂的话题中。

  • 音频中使用的类比非常引人注目且迷人。以我的 Medium 文章为例,它能够将一个相对冷门(即:无聊的)话题(生成 AI 的扩展性挑战可能对非相关领域的听众不具吸引力)与日常生活中的事物进行联系。

    例如,在某个时刻,主持人讨论了生成式 AI 代币成本,并提供了一个更易理解的例子,将这些成本与移动游戏中的微交易进行对比。类似地,他们用提供完整食谱及测量单位的例子来解释提示工程,而不是简单地说“做一顿美味的饭”。他们还用汽车记住常走路线的类比来解释大型语言模型(LLM)的缓存。

  • 两位主持人互相衔接句子的方式非常自然,过渡非常流畅。例如,使用“说到...”这样的短语引入新话题显得非常自然,并没有强行插入的感觉。

  • 在恰到好处的时刻强调某些词语有助于吸引听众的注意力。“哇哦”、“哎呀”和“啊”的表达传达了对另一位主持人刚刚说的内容的真实惊讶。自然的停顿,思考正确的词语,使对话听起来像是自发的,而非排练过的。

  • 在对几篇深度学习论文进行测试后,我可以自信地说,这将成为解释复杂研究的游戏规则改变者,尤其是那些能从类比和“像五岁孩子一样解释”(ELI5)示例中受益的研究。事实上,他们预先准备的示例笔记本中的指导方针之一,名为Introduction to NotebookLM,明确表示该工具是为研究人员、记者、学生和商业专业人士设计的。

在了解了关键优势之后,还有一些局限性需要考虑:

  • 有时,主持人之间的对话感觉不真实。很经常,他们会接对方的话,尽管第一位主持人刚刚要求第二位主持人解释一个新概念,但几秒钟后,第一位主持人却自己回答了自己的问题。

  • 并非所有输入源都能产生相同质量的音频。在进行压力测试时,我尝试输入了另一档播客的文字稿,主持人似乎更倾向于互相发出幽默的声音——“yayaya”,“哦,是的”,“嗯”,“呃-huh”,“对”,“明白”等等!

  • 在讨论某个话题时,拥有大量类比的唯一缺点是,有时候 AI 会将类比解释错误。例如,在讨论关于预测指标的博客时,AI 使用了这样的类比:“就像在学校中,较低的分数通常是更好的,意味着你的预测更接近现实。”

    这种幻觉现象在不同的生成式 AI 模型中很常见,并且在他们的工具中也作为免责声明加以说明。如果我们提供一个非常小众且高度专业化的话题,例如微小 RNA 在基因调控中的作用(这是 2024 年诺贝尔奖获奖的主题),这种现象可能会更加明显。在这种情况下,由于缺乏相关的内在知识,AI 可能会通过使用类比而开始产生幻觉🤷‍♀。

  • 对于非常大的文本,播客往往会突然结束。这表明训练数据可能存在一个截断点,超过这个点后,音频无法适应并提供一个流畅、自然的结尾。

  • (非常小的)一些词,主要是缩写,在音频中被弄乱了。不知为何,RAG 被发音成 ArrrR-G,而不是 R-A-G 这样的单个字母。

  • 有时候,主持人之间过于同意对方,使用像“right”和“exactly”这样的填充词,而另一位主持人还在讲话。这种情况可能会让人感觉像是在强迫回应;我的意思是,让那位可怜的家伙把话说完!

现在我们已经讨论了优缺点,接下来我们来谈谈那个价值百万美元的问题:这项新技术足够给播客主持人带来严峻的竞争吗?

我的简单回答是——还不行。原因呢?就是我们刚才讨论的那些问题。而我知道有些人可能不同意,认为这些问题微不足道,你们说得对。如果你只听一个播客,你可能根本注意不到这些问题,但如果你持续听多个节目,尤其是每天或每周听,你会发现大量的类比和“exactly”会让人感觉不堪重负。正因为如此,也许谷歌在最初发布时并没有将其定位为一个播客工具。

话虽如此,它无疑会降低许多希望探索这个领域但因各种原因不愿使用自己声音的人的入门门槛。更重要的是,我认为它的使用方式是让复杂的主题以易于消化的格式呈现。

最后的思考

Audio Overview 功能的首次发布确实令人印象深刻,超出了我的预期。就个人而言,优点绝对大于缺点,我可以看到这项技术如何在医学研究、法律文件,甚至企业的入职指南等领域带来变革——基本上,任何有乏味文本的领域,都能通过这种技术对听众产生增强效果。

话虽如此,我对未来发布的版本还是有一些期待。首先,最重要的是,希望能有一个 API,允许我绕过 UI,快速将其与我的其他工具进行集成。其次,我希望能够调整输出音频的时长,最后,希望有一个选项来定制类比的上下文/主题(以及数量),以更好地符合我的兴趣。例如,如果我是一个视频游戏爱好者,我会希望类比涉及游戏机制;而如果我是一个厨师,我更喜欢与烹饪技巧相关的类比。

总体来说,这项创新有潜力改变我们沟通复杂信息的方式。如果你已经尝试过,我很想听听你的想法!

快乐学习 ✨

少即是多?深度学习预测模型需要特征减少吗?

原文:towardsdatascience.com/is-less-more-do-deep-learning-forecasting-models-need-feature-reduction-25d8968ac15c?source=collection_archive---------7-----------------------#2024-09-30

是否需要筛选特征,这是一个问题

Philippe Ostiguy, M. Sc.Towards Data Science Philippe Ostiguy, M. Sc.

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

--

AI 图像由作者在 MidJourney V6.1 上创建。

时间序列预测是数据科学中的一项强大工具,通过历史模式提供未来趋势的洞察。在我们之前的文章中,我们探讨了 如何使你的时间序列自动平稳化,这能显著提高模型性能。但平稳性只是解决方案的一部分。随着我们继续优化预测模型,另一个关键问题出现了:如何处理数据可能呈现的众多特征?

当你处理时间序列数据时,你经常会发现自己面临着许多可能的特征,需要将其包含在模型中。虽然使用所有可用数据很有诱惑力,但添加更多特征并不总是更好的选择。这可能会使你的模型变得更加复杂,训练速度变慢,而不一定会提高模型的性能。

你可能在想:简化特征集是否重要,现有的技术有哪些?这正是我们将在本文中讨论的内容。

这里是我们将要讨论的内容的简要总结:

  • 时间序列中的特征减少我们将解释时间序列分析中特征减少的概念以及它的重要性。

  • 实际实施指南通过使用 Python,我们将逐步讲解如何评估和选择时间序列模型的特征,提供优化你方法的实用工具。我们还将评估是否需要削减特征以优化我们的预测模型。

一旦你熟悉了像平稳性和特征减少这样的技术,并且想进一步提升你的模型?可以查看这篇关于在深度学习模型中使用自定义验证损失的文章,以获得更好的股票预测——这是一个很好的下一步!

时间序列中的特征减少:一个简单的解释

特征减少就像是整理工作区,使得你更容易找到所需的东西。在时间序列分析中,这意味着减少模型用于预测的输入变量(特征)的数量。目标是简化模型,同时保留其预测能力。这一点非常重要,因为过多的特征和相关特征会使模型变得复杂、缓慢并降低准确性。

具体来说,简化特征集可以:

  • 降低复杂度:特征较少意味着模型更简单,这通常使得训练和使用更快。

  • 提高泛化能力:通过去除噪声、消除相关特征并聚焦于关键信息,它帮助模型学习真实的潜在模式,而不是记住冗余信息。这增强了模型将预测推广到不同数据集的能力。

  • 更易解释:特征较少的模型通常更容易让人类理解和解释。

  • 计算效率:较少的特征需要更少的内存和处理能力,这对于大数据集或实时应用至关重要。

还需要注意的是,大多数用于时间序列预测的 Python 包并不会自动进行特征减少。这是你通常需要在使用这些包之前自行处理的一个步骤。

为了更好地理解这些概念,让我们通过一个实际的例子来使用来自美国联邦储备经济数据(FRED)数据库的真实世界日数据。我们在这里跳过数据获取的过程,因为我们已经在上一篇文章中详细介绍了如何通过 FRED API 获取免费的可靠数据。你可以通过这个脚本获取我们将使用的数据。一旦你获取了数据:

  • 在当前目录中创建一个data目录
mkdir -p /path/to/current_directory/data
  • 将数据复制到你的目录中
cp -R /path/to/fetcher_directory /path/to/current_directory/data

现在我们有了数据,让我们深入探讨特征减少的示例。

我们之前在另一篇文章中展示了如何清理从 FRED API 获取的每日数据,因此我们在这里跳过该过程,直接使用文章中步骤得到的processed_dataframes(数据框列表)。

import pandas as pd
import os
import warnings
warnings.filterwarnings("ignore")

def is_sp500(df):
    last_date = df['ds'].max()
    last_value = float(df.loc[df['ds'] == last_date, 'value'].iloc[0])
    return 5400 <= last_value <= 5650

initial_model_train = None
for i, df in enumerate(processed_dataframes):
    if df['value'].isna().any():
        continue
    if is_sp500(df):
        initial_model_train = df
        break

TRAIN_SIZE = .90
START_DATE = '2018-10-01'
END_DATE = '2024-09-05'
initial_model_train = initial_model_train.rename(columns={'value': 'price'}).reset_index(drop=True)
initial_model_train['unique_id'] = 'SPY'
initial_model_train['price'] = initial_model_train['price'].astype(float)
initial_model_train['y'] = initial_model_train['price'].pct_change()

initial_model_train = initial_model_train[initial_model_train['ds'] > START_DATE].reset_index(drop=True)
combined_df_all = pd.concat([df.drop(columns=['ds']) for df in processed_dataframes], axis=1)
combined_df_all.columns = [f'value_{i}' for i in range(len(processed_dataframes))]
rows_to_keep = len(initial_model_train)
combined_df_all = combined_df_all.iloc[-rows_to_keep:].reset_index(drop=True)

train_size = int(len(initial_model_train)*TRAIN_SIZE)
initial_model_test = initial_model_train[train_size:]
initial_model_train = initial_model_train[:train_size]
combined_df_test = combined_df_all[train_size:]
combined_df_train = combined_df_all[:train_size]

你可能会问,为什么我们将数据划分为训练集和测试集?原因是为了确保在应用任何转换或降维技术之前不会发生数据泄漏。

initial_model_data包含标准普尔 500 的每日价格(最初存储在processed_dataframes中),这将是我们尝试预测的数据。

接下来,我们需要确保我们的数据是平稳的。关于如何自动使数据平稳并提高 20%模型表现的详细解释,请参阅这篇文章

import numpy as np
from statsmodels.tsa.stattools import adfuller
P_VALUE = 0.05

def replace_inf_nan(series):
    if np.isnan(series.iloc[0]) or np.isinf(series.iloc[0]):
        series.iloc[0] = 0
    mask = np.isinf(series) | np.isnan(series)
    series = series.copy()
    series[mask] = np.nan
    series = series.ffill()
    return series

def safe_convert_to_numeric(series):
    return pd.to_numeric(series, errors='coerce')

tempo_df = pd.DataFrame()
stationary_df_train = pd.DataFrame()
stationary_df_test = pd.DataFrame()

value_columns = [col for col in combined_df_all.columns if col.startswith('value_')]

transformations = ['first_diff', 'pct_change', 'log', 'identity']

def get_first_diff(numeric_series):
    return replace_inf_nan(numeric_series.diff())

def get_pct_change(numeric_series):
    return replace_inf_nan(numeric_series.pct_change())

def get_log_transform(numeric_series):
    return replace_inf_nan(np.log(numeric_series.replace(0, np.nan)))

def get_identity(numeric_series):
    return numeric_series

for index, val_col in enumerate(value_columns):
    numeric_series = safe_convert_to_numeric(combined_df_train[val_col])
    numeric_series_all = safe_convert_to_numeric(combined_df_all[val_col])

    if numeric_series.isna().all():
        continue

    valid_transformations = []

    tempo_df['first_diff'] = get_first_diff(numeric_series)
    tempo_df['pct_change'] = get_pct_change(numeric_series)
    tempo_df['log'] = get_log_transform(numeric_series)
    tempo_df['identity'] = get_identity(numeric_series)

    for transfo in transformations:
        tempo_df[transfo] = replace_inf_nan(tempo_df[transfo])
        series = tempo_df[transfo].dropna()

        if len(series) > 1 and not (series == series.iloc[0]).all():
            result = adfuller(series)
            if result[1] < P_VALUE:
                valid_transformations.append((transfo, result[0], result[1]))

    if valid_transformations:
        if any(transfo == 'identity' for transfo, _, _ in valid_transformations):
            chosen_transfo = 'identity'
        else:
            chosen_transfo = min(valid_transformations, key=lambda x: x[1])[0]

        if chosen_transfo == 'first_diff':
            stationary_df_train[val_col] = get_first_diff(numeric_series_all)
        elif chosen_transfo == 'pct_change':
            stationary_df_train[val_col] = get_pct_change(numeric_series_all)
        elif chosen_transfo == 'log':
            stationary_df_train[val_col] = get_log_transform(numeric_series_all)
        else:
            stationary_df_train[val_col] = get_identity(numeric_series_all)

    else:
        print(f"No valid transformation found for {val_col}")

stationary_df_test = stationary_df_train[train_size:]
stationary_df_train = stationary_df_train[:train_size]

initial_model_train = initial_model_train.iloc[1:].reset_index(drop=True)
stationary_df_train = stationary_df_train.iloc[1:].reset_index(drop=True)
last_train_index = stationary_df_train.index[-1]
stationary_df_test = stationary_df_test.loc[last_train_index + 1:].reset_index(drop=True)
initial_model_test = initial_model_test.loc[last_train_index + 1:].reset_index(drop=True)

然后,我们将统计至少与另一个变量有 95%相关系数的变量数量。

CORR_COFF = .95
corr_matrix = stationary_df_train.corr().abs()
mask = np.triu(np.ones_like(corr_matrix, dtype=bool), k=1)
high_corr = corr_matrix.where(mask).stack()
high_corr = high_corr[high_corr >= CORR_COFF]
unique_cols = set(high_corr.index.get_level_values(0)) | set(high_corr.index.get_level_values(1))
num_high_corr_cols = len(unique_cols)

print(f"\n{num_high_corr_cols}/{stationary_df_train.shape[1]} variables have ≥{int(CORR_COFF*100)}% "
      f"correlation with another variable.\n")

高度相关的变量数量。图像由作者提供。

如果 438 个变量中有 260 个变量与至少另一个变量的相关性达到 95%或更高,这可能是一个问题。这表明数据集中存在显著的多重共线性。这种冗余可能会导致以下几个问题:

  • 使模型变得复杂,但并未增加实质性的新信息

  • 可能导致回归系数估计的不稳定

  • 增加过拟合的风险

  • 使得解释单个变量的影响变得困难

特征评估与选择

我们理解特征降维可能很重要,但我们该如何进行呢?我们应该使用哪些技术?这些是我们现在要探讨的问题。

我们将要探讨的第一种技术是主成分分析(PCA)。这是一种常见且有效的降维技术。PCA 识别特征之间的线性关系,并保留解释原始数据集中预定百分比方差的主成分。在我们的使用案例中,我们将EXPLAINED_VARIANCE阈值设置为 90%。

from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
EXPLAINED_VARIANCE = .9
MIN_VARIANCE = 1e-10

X_train = stationary_df_train.values
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
pca = PCA(n_components=EXPLAINED_VARIANCE, svd_solver='full')
X_train_pca = pca.fit_transform(X_train_scaled)

components_to_keep = pca.explained_variance_ > MIN_VARIANCE
X_train_pca = X_train_pca[:, components_to_keep]

pca_df_train = pd.DataFrame(
    X_train_pca,
    columns=[f'PC{i+1}' for i in range(X_train_pca.shape[1])]
)

X_test = stationary_df_test.values
X_test_scaled = scaler.transform(X_test)
X_test_pca = pca.transform(X_test_scaled)

X_test_pca = X_test_pca[:, components_to_keep]

pca_df_test = pd.DataFrame(
    X_test_pca,
    columns=[f'PC{i+1}' for i in range(X_test_pca.shape[1])]
)

print(f"\nOriginal number of features: {stationary_df_train.shape[1]}")
print(f"Number of components after PCA: {pca_df_train.shape[1]}\n")

使用主成分分析进行特征降维。图像由作者提供。

令人印象深刻的是:在降维后,438 个特征中只剩下 76 个成分,同时保持了 90%的方差解释!接下来我们将转向一种非线性降维技术。

时间序列融合变换器(TFT)是一种用于时间序列预测的先进模型。它包含变量选择网络(VSN),这是模型的一个关键组件。它专门设计用于自动识别和关注数据集中的最相关特征。通过为每个输入变量分配学习到的权重,它有效地突出哪些特征对预测任务的贡献最大。

基于 VSN 的方法将是我们第二种降维技术。我们将使用PyTorch Forecasting来实现,它允许我们利用 TFT 模型中的变量选择网络。

我们将使用一个基础配置。我们的目标不是创建性能最强的模型,而是识别最相关的特征,同时尽量减少资源使用。

from pytorch_forecasting import TemporalFusionTransformer, TimeSeriesDataSet
from pytorch_forecasting.metrics import QuantileLoss
from lightning.pytorch.callbacks import EarlyStopping
import lightning.pytorch as pl
import torch

pl.seed_everything(42)
max_encoder_length = 32
max_prediction_length = 1
VAL_SIZE = .2
VARIABLES_IMPORTANCE = .8
model_data_feature_sel = initial_model_train.join(stationary_df_train)
model_data_feature_sel = model_data_feature_sel.join(pca_df_train)
model_data_feature_sel['price'] = model_data_feature_sel['price'].astype(float)
model_data_feature_sel['y'] = model_data_feature_sel['price'].pct_change()
model_data_feature_sel = model_data_feature_sel.iloc[1:].reset_index(drop=True)

model_data_feature_sel['group'] = 'spy'
model_data_feature_sel['time_idx'] = range(len(model_data_feature_sel))

train_size_vsn = int((1-VAL_SIZE)*len(model_data_feature_sel))
train_data_feature = model_data_feature_sel[:train_size_vsn]
val_data_feature = model_data_feature_sel[train_size_vsn:]
unknown_reals_origin = [col for col in model_data_feature_sel.columns if col.startswith('value_')] + ['y']

timeseries_config = {
    "time_idx": "time_idx",
    "target": "y",
    "group_ids": ["group"],
    "max_encoder_length": max_encoder_length,
    "max_prediction_length": max_prediction_length,
    "time_varying_unknown_reals": unknown_reals_origin,
    "add_relative_time_idx": True,
    "add_target_scales": True,
    "add_encoder_length": True
}

training_ts = TimeSeriesDataSet(
    train_data_feature,
    **timeseries_config
)

VARIABLES_IMPORTANCE阈值设置为 0.8,这意味着我们将保留由变量选择网络(VSN)确定的排名前 80%的重要特征。有关时间序列融合变换器(TFT)及其参数的更多信息,请参考文档

接下来,我们将训练 TFT 模型。

if torch.cuda.is_available():
    accelerator = 'gpu'
    num_workers = 2
else :
    accelerator = 'auto'
    num_workers = 0

validation = TimeSeriesDataSet.from_dataset(training_ts, val_data_feature, predict=True, stop_randomization=True)
train_dataloader = training_ts.to_dataloader(train=True, batch_size=64, num_workers=num_workers)
val_dataloader = validation.to_dataloader(train=False, batch_size=64*5, num_workers=num_workers)

tft = TemporalFusionTransformer.from_dataset(
    training_ts,
    learning_rate=0.03,
    hidden_size=16,
    attention_head_size=2,
    dropout=0.1,
    loss=QuantileLoss()
)

early_stop_callback = EarlyStopping(monitor="val_loss", min_delta=1e-5, patience=5, verbose=False, mode="min")

trainer = pl.Trainer(max_epochs=20,  accelerator=accelerator, gradient_clip_val=.5, callbacks=[early_stop_callback])
trainer.fit(
    tft,
    train_dataloaders=train_dataloader,
    val_dataloaders=val_dataloader

)

我们故意设置了max_epochs=20,以避免模型训练时间过长。此外,我们还实现了early_stop_callback,如果模型在连续 5 个 epoch 中没有改善,将停止训练(patience=5)。

最后,使用获得的最佳模型,我们选择由 VSN 确定的最重要特征的 80 百分位。

best_model_path = trainer.checkpoint_callback.best_model_path
best_tft = TemporalFusionTransformer.load_from_checkpoint(best_model_path)

raw_predictions = best_tft.predict(val_dataloader, mode="raw", return_x=True)

def get_top_encoder_variables(best_tft,interpretation):
    encoder_importances = interpretation["encoder_variables"]
    sorted_importances, indices = torch.sort(encoder_importances, descending=True)
    cumulative_importances = torch.cumsum(sorted_importances, dim=0)
    threshold_index = torch.where(cumulative_importances > VARIABLES_IMPORTANCE)[0][0]
    top_variables = [best_tft.encoder_variables[i] for i in indices[:threshold_index+1]]
    if 'relative_time_idx' in top_variables:
        top_variables.remove('relative_time_idx')
    return top_variables

interpretation= best_tft.interpret_output(raw_predictions.output, reduction="sum")
top_encoder_vars = get_top_encoder_variables(best_tft,interpretation)

print(f"\nOriginal number of features: {stationary_df_train.shape[1]}")
print(f"Number of features after Variable Selection Network (VSN): {len(top_encoder_vars)}\n")

使用变量选择网络进行特征降维。图片由作者提供。

原始数据集包含 438 个特征,在应用 VSN 方法后,仅剩下 1 个特征!这种剧烈的降维暗示了几种可能性:

  1. 许多原始特征可能是冗余的。

  2. 特征选择过程可能已过度简化数据。

  3. 仅使用目标变量的历史值(自回归方法)可能与包含外生变量的模型表现相当,甚至可能更好。

评估特征降维技术

在最后一节中,我们将比较应用于模型的降维技术。每种方法在保持相同模型配置的同时进行测试,唯一区别是所选特征的降维处理。

我们将使用TiDE,这是一种基于 Transformer 的小型最先进模型。我们将使用NeuralForecast提供的实现。只要它允许外生历史变量,NeuralForecast 的任何模型这里都可以使用。

我们将使用每日 SPY(标普 500ETF)数据训练和测试两个模型。两个模型将具有相同的:

  1. 训练-测试分割比例

  2. 超参数

  3. 单一时间序列(SPY)

  4. 预测范围为一步 ahead

这两个模型之间唯一的区别是特征减少技术。就这样!

  1. 第一个模型:原始特征(没有特征减少)

  2. 第二个模型:使用 PCA 进行特征减少

  3. 第三个模型:使用 VSN 进行特征减少

该设置使我们能够隔离每种特征减少技术对模型性能的影响。

首先,我们使用相同的配置训练三个模型,唯一的区别是特征。

from neuralforecast.models import TiDE
from neuralforecast import NeuralForecast

train_data = initial_model_train.join(stationary_df_train)
train_data = train_data.join(pca_df_train)
test_data = initial_model_test.join(stationary_df_test)
test_data = test_data.join(pca_df_test)

hist_exog_list_origin = [col for col in train_data.columns if col.startswith('value_')] + ['y']
hist_exog_list_pca = [col for col in train_data.columns if col.startswith('PC')] + ['y']
hist_exog_list_vsn = top_encoder_vars

tide_params = {
    "h": 1,
    "input_size": 32,
    "scaler_type": "robust",
    "max_steps": 500,
    "val_check_steps": 20,
    "early_stop_patience_steps": 5
}

model_original = TiDE(
    **tide_params,
    hist_exog_list=hist_exog_list_origin,
)

model_pca = TiDE(
    **tide_params,
    hist_exog_list=hist_exog_list_pca,
)

model_vsn = TiDE(
    **tide_params,
    hist_exog_list=hist_exog_list_vsn,
)

nf = NeuralForecast(
    models=[model_original, model_pca, model_vsn],
    freq='D'
)

val_size = int(train_size*VAL_SIZE)
nf.fit(df=train_data,val_size=val_size,use_init_models=True)

然后,我们进行预测。

from tabulate import tabulate
y_hat_test_ret = pd.DataFrame()
current_train_data = train_data.copy()

y_hat_ret = nf.predict(current_train_data)
y_hat_test_ret = pd.concat([y_hat_test_ret, y_hat_ret.iloc[[-1]]])

for i in range(len(test_data) - 1):
    combined_data = pd.concat([current_train_data, test_data.iloc[[i]]])
    y_hat_ret = nf.predict(combined_data)
    y_hat_test_ret = pd.concat([y_hat_test_ret, y_hat_ret.iloc[[-1]]])
    current_train_data = combined_data

predicted_returns_original = y_hat_test_ret['TiDE'].values
predicted_returns_pca = y_hat_test_ret['TiDE1'].values
predicted_returns_vsn = y_hat_test_ret['TiDE2'].values

predicted_prices_original = []
predicted_prices_pca = []
predicted_prices_vsn = []

for i in range(len(predicted_returns_pca)):
    if i == 0:
        last_true_price = train_data['price'].iloc[-1]
    else:
        last_true_price = test_data['price'].iloc[i-1]
    predicted_prices_original.append(last_true_price * (1 + predicted_returns_original[i]))
    predicted_prices_pca.append(last_true_price * (1 + predicted_returns_pca[i]))
    predicted_prices_vsn.append(last_true_price * (1 + predicted_returns_vsn[i]))

true_values = test_data['price']
methods = ['Original','PCA', 'VSN']
predicted_prices = [predicted_prices_original,predicted_prices_pca, predicted_prices_vsn]

results = []

for method, prices in zip(methods, predicted_prices):
    mse = np.mean((np.array(prices) - true_values)**2)
    rmse = np.sqrt(mse)
    mae = np.mean(np.abs(np.array(prices) - true_values))

    results.append([method, mse, rmse, mae])

headers = ["Method", "MSE", "RMSE", "MAE"]
table = tabulate(results, headers=headers, floatfmt=".4f", tablefmt="grid")

print("\nPrediction Errors Comparison:")
print(table)

with open("prediction_errors_comparison.txt", "w") as f:
    f.write("Prediction Errors Comparison:\n")
    f.write(table)

我们使用模型预测每日回报,然后将这些回报转换为价格。这种方法使我们能够通过价格计算预测误差,并将实际价格与预测价格进行比较,呈现在图表中。

使用不同特征减少技术的预测误差比较。图片由作者提供。

TiDE 模型在原始和减少特征集上表现相似,揭示了一个关键的见解:特征减少并没有像预期那样提高预测精度。这表明可能存在一些关键问题:

  • 信息损失:尽管目的是保留重要数据,但降维技术丢弃了与预测任务相关的信息,这解释了在减少特征时预测没有改进。

  • 泛化困难:不同特征集之间的一致表现表明模型在捕捉潜在模式方面存在困难,无论特征数量多少。

  • 复杂性过度:使用较少特征获得类似结果表明,TiDE 的复杂架构可能过于复杂。一种更简单的模型,例如 ARIMA,可能表现得同样好。

然后,让我们查看图表,看看是否能观察到三种预测方法与实际价格之间有任何显著差异。

import matplotlib.pyplot as plt

plt.figure(figsize=(12, 6))
plt.plot(train_data['ds'], train_data['price'], label='Training Data', color='blue')
plt.plot(test_data['ds'], true_values, label='True Prices', color='green')
plt.plot(test_data['ds'], predicted_prices_original, label='Predicted Prices', color='red')
plt.legend()
plt.title('SPY Price Forecast Using All Original Feature')
plt.xlabel('Date')
plt.ylabel('SPY Price')
plt.savefig('spy_forecast_chart_original.png', dpi=300, bbox_inches='tight')
plt.close()

plt.figure(figsize=(12, 6))
plt.plot(train_data['ds'], train_data['price'], label='Training Data', color='blue')
plt.plot(test_data['ds'], true_values, label='True Prices', color='green')
plt.plot(test_data['ds'], predicted_prices_pca, label='Predicted Prices', color='red')
plt.legend()
plt.title('SPY Price Forecast Using PCA Dimensionality Reduction')
plt.xlabel('Date')
plt.ylabel('SPY Price')
plt.savefig('spy_forecast_chart_pca.png', dpi=300, bbox_inches='tight')
plt.close()

plt.figure(figsize=(12, 6))
plt.plot(train_data['ds'], train_data['price'], label='Training Data', color='blue')
plt.plot(test_data['ds'], true_values, label='True Prices', color='green')
plt.plot(test_data['ds'], predicted_prices_vsn, label='Predicted Prices', color='red')
plt.legend()
plt.title('SPY Price Forecast Using VSN')
plt.xlabel('Date')
plt.ylabel('SPY Price')
plt.savefig('spy_forecast_chart_vsn.png', dpi=300, bbox_inches='tight')
plt.close()

使用所有原始特征的 SPY 价格预测。图片由作者提供。

使用 PCA 的 SPY 价格预测。图片由作者提供。

使用 VSN 的 SPY 价格预测。图片由作者提供。

真实价格与预测价格之间的差异在三个模型中看起来一致,性能之间没有明显的差异。

结论

我们做到了!我们探讨了特征减少在时间序列分析中的重要性,并提供了一个实用的实现指南:

  • 特征降维的目标是简化模型,同时保持预测能力。其好处包括减少复杂性、提高泛化能力、简化解释和计算效率。

  • 我们使用 FRED 数据演示了两种降维技术:

  1. 主成分分析(PCA),一种线性降维方法,将特征从 438 个减少到 76 个,同时保留了 90%的解释方差。

  2. 来自时序融合变换器(Temporal Fusion Transformers)的变量选择网络(VSN),一种非线性方法,通过设置 80 百分位重要性阈值,将特征大幅减少到仅剩 1 个。

  • 使用 TiDE 模型进行的评估表明,原始特征集和降维后的特征集在性能上相似,这表明特征降维并不总是能提升预测性能。这可能是由于降维过程中信息丢失、模型难以捕捉潜在模式,或可能是对于这个特定的预测任务,更简单的模型同样有效。

最后一点,我们并没有探索所有的特征降维技术,例如 SHAP(SHapley Additive exPlanations),它提供了跨多种模型类型的统一特征重要性度量。即使我们没有改进模型,进行特征筛选并比较不同降维方法的性能仍然是值得的。这种方法有助于确保在优化模型效率和可解释性的同时,不丢失有价值的信息。

在未来的文章中,我们将把这些特征降维技术应用于更复杂的模型,比较它们对性能和可解释性的影响。敬请关注!

准备将这些概念付诸实践了吗?你可以在这里找到完整的代码实现。

喜欢这篇文章吗?表示你的支持!

👏 鼓掌 50 次

🤝 通过LinkedIn向我发送连接请求,保持联系

你的支持意义重大! 🙏

LLM 的表现是否由其基因编码预先决定?

原文:towardsdatascience.com/is-llm-performance-predetermined-by-their-genetic-code-74e7bb080dab?source=collection_archive---------6-----------------------#2024-07-08

|LLM|AI|GENETIC|

探索基因算法预测大型语言模型的未来

Salvatore RaieliTowards Data Science Salvatore Raieli

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

--

图像由作者使用 AI 生成

我对基因是数字化的这一理念感到着迷。基因是由一长串编码字母组成的序列,就像计算机中的信息一样。现代生物学正在逐渐成为信息技术的一个分支。 — 理查德·道金斯

目前有大量的大型语言模型(LLMs),无论是闭源还是开源,每天都有数百个在Hugging Face Hub上发布。这不仅展示了社区的兴趣,也证明了语言模型的成功。另一方面,尽管存在这些兴趣,大多数模型并未进行基准测试,而且相关细节较少(缺乏透明度)。

## 变换器的挽歌?

变换器(Transformer)模型将引领我们进入通用人工智能的时代吗?还是将被取代?

towardsdatascience.com

Matplotlib 仍然是最佳的静态图表绘图库吗?

原文:towardsdatascience.com/is-matplotlib-still-the-best-python-library-for-static-plots-a933c092cd49?source=collection_archive---------0-----------------------#2024-01-20

可视化

这个无处不在的原版仍然是大师吗?

Mike ClaytonTowards Data Science Mike Clayton

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

--

一个 Matplotlib 散点图 — 图像来自作者

Matplotlib 可能是每个数据科学家或数据分析师第一次接触的绘图库,如果他们使用 Python 编程语言的话。它似乎无处不在。

…那么,Matplotlib 是否因为它是最好的可用选项而如此普及?还是它只是存在了很长时间?有哪些替代品,它们如何比较?

介绍

在文章标题中,您可能注意到我特别提到了静态图表

静态与动态

尽管仪表板和交互式图表在探索和展示数据方面极为重要,但静态图表始终是必不可少的。

报告、技术论文、文章以及所有印刷材料总是需要静态图表。因此,能够快速、轻松地制作清晰、逻辑性强且美观的静态图表仍然是绝对必要的。考虑到这一点,本文将主要关注静态图表。

静态图表带来了额外的挑战

多重共线性是否正在破坏你在市场营销组合建模中的因果推断?

原文:towardsdatascience.com/is-multi-collinearity-destroying-your-causal-inferences-in-marketing-mix-modelling-78cb56017c73?source=collection_archive---------1-----------------------#2024-09-10

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

Ryan O'SullivanTowards Data Science Ryan O'Sullivan

·发表于Towards Data Science ·阅读时长 16 分钟·2024 年 9 月 10 日

--

图片来自NOAAUnsplash

本系列内容是什么?

欢迎来到我的因果 AI 系列,我们将在这里探讨因果推理与机器学习模型的整合。预计将探索在不同商业环境中的多个实际应用。

在上一篇文章中,我们探讨了使用 CUPED 和双重机器学习驱动实验。今天,我们将重点讨论多重共线性如何破坏你在市场营销组合建模中的因果推断。

如果你错过了上一篇关于使用 CUPED 和双重机器学习驱动实验的文章,可以在这里查看:

## 使用 CUPED 和双重机器学习驱动实验

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

towardsdatascience.com

介绍

在本文中,我们将探讨多重共线性可能带来的危害,并评估一些可以用来应对它的方法。以下方面将被覆盖:

  • 什么是多重共线性?

  • 为什么它在因果推断中是一个问题?

  • 为什么它在市场营销组合建模中如此常见?

  • 我们如何检测它?

  • 我们该如何解决这个问题?

  • 贝叶斯先验简介。

  • 这是一个 Python 案例研究,探讨了贝叶斯先验和随机预算调整如何有助于缓解多重共线性。

完整的笔记本可以在此处找到:

[## causal_ai/notebooks/is multi-collinearity destroying your mmm.ipynb at main · raz1470/causal_ai

本项目介绍了因果人工智能以及它如何驱动商业价值。 - causal_ai/notebooks/is multi-collinearity…

github.com](https://github.com/raz1470/causal_ai/blob/main/notebooks/is multi-collinearity destroying your mmm.ipynb?source=post_page-----78cb56017c73--------------------------------)

什么是多重共线性?

多重共线性发生在回归模型中的两个或更多自变量之间高度相关时。这种高度的相关性意味着它们提供了重叠的信息,使得模型难以区分每个变量的独立效应。

让我们以市场营销为例。你销售的产品需求高度季节性——因此,在需求高峰期间增加营销支出是有意义的。然而,如果电视和社交媒体支出都遵循相同的季节性模式,那么模型就会难以准确确定每个渠道的独立贡献。

用户生成的图片

为什么它在因果推断中是一个问题?

多重共线性可能导致相关变量的系数变得不稳定和偏倚。当存在多重共线性时,回归系数的标准误差往往会膨胀。这意味着估计的 uncertainty 增加,使得很难判断一个变量是否真的具有显著性。

让我们回到市场营销的例子,即使电视广告和社交媒体都推动了销售,模型可能也很难分离它们的影响,因为膨胀的标准误差使得系数估计不可靠。

我们可以通过在 Python 中模拟一些例子来更好地理解。请注意我们如何将社交媒体支出和电视支出的系数分别设置为 0.10 和 0.20。

例子 1 —— 每个渠道的营销支出相等,导致系数偏倚:

# Example 1 - marketing spend on each channel is equal: biased coefficients
np.random.seed(150)

tv_spend = np.random.normal(0, 50, 1000)
social_spend = tv_spend
sales = 0.10 * tv_spend + 0.20 * social_spend
X = np.column_stack((tv_spend, social_spend))
clf = LinearRegression()
clf.fit(X, sales)

print(f'Coefficients: {clf.coef_}')

用户生成的图片

例子 2 —— 每个渠道的营销支出遵循相同的趋势,这次导致系数符号翻转:

# Example 2 - marketing spend on each channel follows the same trend: biased coefficients and sign flip
np.random.seed(150)

tv_spend = np.random.normal(0, 50, 1000)
social_spend = tv_spend * 0.50
sales = 0.10 * tv_spend + 0.20 * social_spend
X = np.column_stack((tv_spend, social_spend))
clf = LinearRegression()
clf.fit(X, sales)

print(f'Coefficients: {clf.coef_}')

用户生成的图片

例子 3 —— 添加随机噪声使得模型能够估计正确的系数:

# Example 3 - random noise added to marketing spend: correct coefficients
np.random.seed(150)

tv_spend = np.random.normal(0, 50, 1000)
social_spend = tv_spend * 0.50 + np.random.normal(0, 1, 1000)
sales = 0.10 * tv_spend + 0.20 * social_spend
X = np.column_stack((tv_spend, social_spend))
clf = LinearRegression()
clf.fit(X, sales)

print(f'Coefficients: {clf.coef_}')

用户生成的图片

此外,多重共线性还可能导致所谓的符号翻转现象,即变量的效应方向(正向或负向)可能会意外反转。例如,尽管你知道社交媒体广告应该对销售产生正面影响,但模型可能会显示一个负系数,原因仅仅是它与电视支出的高度相关性。我们可以在示例 2 中看到这一点。

为什么在营销混合建模中如此常见?

我们已经提到一个关键问题:营销团队通常对需求模式有较强的理解,并利用这些知识来设定预算。通常,他们会在需求高峰期增加多个渠道的支出。虽然从战略角度来看这很有意义,但也可能无意中造成多重共线性问题。

即便对于需求相对稳定的产品,如果市场营销团队每周或每月按相同百分比调整每个渠道的投入,这也会导致多重共线性问题。

我在营销混合模型(MMM)中见到的另一个多重共线性原因是因果图(DAGs)指定不当。如果我们只是将所有变量放入一个平坦的回归模型中,很可能会遇到多重共线性问题。举个例子——如果付费搜索展示量可以通过电视和社交媒体支出来解释,那么将它与电视和社交媒体一起放入一个平坦的线性回归模型中,很可能会导致多重共线性。

用户生成的图像

我们如何检测它?

检测多重共线性对于防止它扭曲因果推断至关重要。以下是一些常见的识别方法:

相关性

检测多重共线性的一种简单而有效的方法是检查相关矩阵。该矩阵显示了数据集中所有变量之间的成对相关性。如果两个预测变量的相关系数接近+1 或-1,说明它们高度相关,这可能表明存在多重共线性。

方差膨胀因子(VIF)

量化回归系数的方差由于多重共线性而被膨胀的程度:

用户生成的图像

R 平方是通过将所有其他自变量回归到所选变量上得到的。如果 R 平方很高,意味着所选变量可以通过其他自变量来预测(这将导致所选变量的 VIF 很高)。

关于 VIF 检测多重共线性的经验法则阈值——然而,我没有找到任何有说服力的资源来支持这些阈值,因此这里不再引用它们。

标准误差

回归系数的标准误差告诉你该系数的估计精度。它是系数估计方差的平方根。较高的标准误差可能表明存在多重共线性。

仿真

另外,了解上面提到的三种方法是有帮助的,但要量化你是否存在严重的多重共线性问题仍然可能很困难。你可以采取的另一种方法是运行一个已知系数的模拟,然后查看你的模型能多好地估计这些系数。让我们通过一个营销组合模型(MMM)示例来说明:

  • 像往常一样提取渠道支出和销售数据。
-- example SQL code to extract data
select
  observation_date,
  sum(tv_spend) as tv_spend,
  sum(social_spend) as social_spend,
  sum(sales) as sales
from mmm_data_mart
group by
  observation_date;
  • 创建数据生成过程,为每个渠道设置一个系数。
# set coefficients for each channel using actual spend data
marketing_contribution = tv_spend * 0.10 + social_spend * 0.20

# calculate the remaining contribution
other_contribution = sales - marketing_contribution

# create arrays for regression
X = np.column_stack((tv_spend, social_spend, other_contribution))
y = sales
  • 训练模型并将估计的系数与上一步设置的系数进行比较。
# train regression model
clf = LinearRegression()
clf.fit(X, y)

# recover coefficients
print(f'Recovered coefficients: {clf.coef_}')

现在我们知道如何识别多重共线性,接下来让我们探讨如何应对它!

我们如何应对它?

解决多重共线性问题有几种策略:

  1. 去除一个相关变量

    这是一种直接减少冗余的方式。然而,盲目去除一个变量是有风险的——尤其是当去除的变量是混杂变量时。一个有帮助的步骤是确定因果图(DAG)。理解因果关系可以帮助你评估是否去除一个相关变量仍然能够进行有效的推断。

  2. 合并变量

    当两个或多个变量提供相似的信息时,你可以将它们合并。这种方法可以减少模型的维度,缓解多重共线性的风险,同时尽可能保留更多信息。与之前的方法一样,理解数据的因果结构至关重要。

  3. 正则化技术

    正则化方法,如岭回归或套索回归,是应对多重共线性的强大工具。这些技术通过向模型复杂性添加惩罚,缩小相关预测变量的系数。岭回归专注于减少所有系数的大小,而套索回归则可以将某些系数缩小到零,从而有效地选择一个预测变量的子集。

  4. 贝叶斯先验

    使用贝叶斯回归技术,你可以根据现有知识为参数引入先验分布。这使得模型能够基于这些先验进行“正则化”,从而减少多重共线性的影响。通过为参数值提供合理的范围,它可以防止对高度相关变量的过拟合。我们将在案例研究中深入探讨这种方法,以展示它的有效性。

  5. 随机预算调整

    另一种策略,特别是在营销组合建模(MMM)中非常有用,是在渠道层面引入随机调整你的营销预算。通过随机调整预算,你可以开始观察每个渠道的独立效应。这个方法有两个主要挑战:(1) 营销团队的支持,(2) 一旦开始运行,可能需要几个月甚至几年才能收集到足够的数据用于模型。我们也将在案例研究中通过一些模拟来讨论这一点。

接下来我们将在案例研究中测试其中的一些策略。

贝叶斯先验简介

深入探讨贝叶斯先验超出了本文的范围,但为了确保我们能够跟上案例研究,让我们简要介绍一些其背后的直觉。

贝叶斯先验代表我们在观察到任何数据之前对参数值的初步信念。在贝叶斯方法中,我们将这些先验与实际数据(通过似然函数)结合起来,以更新我们的理解并计算后验分布,后者反映了先验信息和数据的结合。

简单来说:在构建 MMM 时,我们需要为每个变量的系数提供一些先验信念。我们不是提供一个固定的上下界,而是提供一个分布。然后模型会在这个分布中搜索,并使用数据来计算后验分布。通常,我们使用该后验分布的均值来获得我们的系数估计。

当然,贝叶斯先验不止这些,但上面的解释为我们提供了一个坚实的起点!

案例研究

你最近加入了一家初创公司,他们已经运行了几年的营销策略。现在他们想通过 MMM 来衡量效果,但他们早期的尝试得出了不合逻辑的结果(电视的贡献是负的!)。看起来他们的问题源于每个营销渠道的负责人都是根据需求预测来设定预算,这导致了多重共线性问题。你的任务是评估情况并提出下一步的建议。

数据生成过程

让我们从创建一个具有以下属性的 Python 数据生成函数开始:

  • 需求由三个部分组成:趋势、季节性和噪音。

  • 需求预测模型来自数据科学团队,能够在 +/- 5% 的准确度内做出预测。

  • 这个需求预测模型由营销团队用来设定社交媒体和电视支出的预算——我们可以通过 spend_rand_change 参数为这些预算添加一些随机变化。

  • 营销团队在电视上的支出是社交媒体的两倍。

  • 销售由需求、社交媒体支出和电视支出的线性组合驱动。

  • 社交媒体和电视支出的系数可以通过 true_coef 参数来设置。

def data_generator(spend_rand_change, true_coef):
    '''
    Generate simulated marketing data with demand, forecasted demand, social and TV spend, and sales.

    Args:
        spend_rand_change (float): Random variation parameter for marketing spend.
    true_coef (list): True coefficients for demand, social media spend, and TV spend effects on sales.

    Returns:
        pd.DataFrame: DataFrame containing the simulated data.
    '''

    # Parameters for data generation
    start_date = "2018-01-01"
    periods = 365 * 3  # Daily data for three years
    trend_slope = 0.01  # Linear trend component
    seasonal_amplitude = 5  # Amplitude of the seasonal component
    seasonal_period = 30.44  # Monthly periodicity
    noise_level = 5  # Level of random noise in demand

    # Generate time variables
    time = np.arange(periods)
    date_range = pd.date_range(start=start_date, periods=periods)

    # Create demand components
    trend_component = trend_slope * time
    seasonal_component = seasonal_amplitude * np.sin(2 * np.pi * time / seasonal_period)
    noise_component = noise_level * np.random.randn(periods)

    # Combine to form demand series
    demand = 100 + trend_component + seasonal_component + noise_component

    # Initialize DataFrame
    df = pd.DataFrame({'date': date_range, 'demand': demand})

    # Add forecasted demand with slight random variation
    df['demand_forecast'] = df['demand'] * np.random.uniform(0.95, 1.05, len(df))

    # Simulate social media and TV spend with random variation
    df['social_spend'] = df['demand_forecast'] * 10 * np.random.uniform(1 - spend_rand_change, 1 + spend_rand_change, len(df))
    df['tv_spend'] = df['demand_forecast'] * 20 * np.random.uniform(1 - spend_rand_change, 1 + spend_rand_change, len(df))
    df['total_spend'] = df['social_spend'] + df['tv_spend']

    # Calculate sales based on demand, social, and TV spend, with some added noise
    df['sales'] = (
        df['demand'] * true_coef[0] + 
        df['social_spend'] * true_coef[1] + 
        df['tv_spend'] * true_coef[2]
    )
    sales_noise = 0.01 * df['sales'] * np.random.randn(len(df))
    df['sales'] += sales_noise

    return df

初步评估

现在让我们模拟一些数据,假设没有对营销团队如何设定预算进行随机变化——我们将尝试估计真实的系数。下面的函数用于训练回归模型:

def run_reg(df, features, target):
    '''
    Runs a linear regression on the specified features to predict the target variable.

    Args:
        df (pd.DataFrame): The input data containing features and target.
    features (list): List of column names to be used as features in the regression.
    target (str): The name of the target column to be predicted.
    Returns:
        np.ndarray: Array of recovered coefficients from the linear regression model.
    '''

    # Extract features and target values
    X = df[features].values
    y = df[target].values

    # Initialize and fit linear regression model
    model = LinearRegression()
    model.fit(X, y)

    # Output recovered coefficients
    coefficients = model.coef_
    print(f'Recovered coefficients: {coefficients}')

    return coefficients
np.random.seed(40)

true_coef = [0.35, 0.15, 0.05]

features = [
    "demand",
    "social_spend",
    "tv_spend"
]

target = "sales"

sim_1 = data_generator(0.00, true_coef)
reg_1 = run_reg(sim_1, features, target)

print(f"True coefficients: {true_coef}")

用户生成的图像

我们可以看到,社交支出的系数被低估,而电视支出的系数被高估。幸好你没有将这个模型交给营销团队来优化他们的预算——否则结果可能会很糟糕!

在短期内,使用贝叶斯先验是否能得到更少偏差的系数?

从长远来看,随机调整预算是否能创建一个不受多重共线性影响的数据集?

让我们来找找看!

贝叶斯先验

让我们从探索贝叶斯先验开始……

我们将使用我最喜欢的 MMM 实现——pymc marketing:

[## Guide - pymc-marketing 0.8.0 文档

编辑描述

www.pymc-marketing.io

我们将使用在初步评估中生成的相同数据:

date_col = "date"

y_col = "sales"

channel_cols = ["social_spend",
                "tv_spend"]

control_cols = ["demand"]

X = sim_1[[date_col] + channel_cols + control_cols]
y = sim_1[y_col]

在开始建模之前,让我们看看每个变量的贡献情况:

# calculate contribution
true_contributions = [round(np.sum(X["demand"] * true_coef[0]) / np.sum(y), 2), 
                      round(np.sum(X["social_spend"] * true_coef[1]) / np.sum(y), 2), 
                      round(np.sum(X["tv_spend"] * true_coef[2]) / np.sum(y), 2)]
true_contributions

用户生成的图片

贝叶斯(默认)先验

让我们看看如果使用默认先验会得到什么结果。下面你可以看到有很多先验!这是因为我们必须为截距、广告库存、饱和度变换等提供先验。我们关注的是饱和度β——这相当于我们试图估算的变量系数。

mmm_default = MMM(
    adstock="geometric",
    saturation="logistic",
    date_column=date_col,
    channel_columns=channel_cols,
    control_columns=control_cols,
    adstock_max_lag=4,
    yearly_seasonality=2,
)

mmm_default.default_model_config

用户生成的图片

我们必须提供一个分布。HalfNormal 是一个合理的选择,用于渠道系数,因为我们知道它们不能为负数。下面我们将可视化这个分布,以帮助理解它:

sigma = 2

x1 = np.linspace(0, 10, 1000)
y1 = halfnorm.pdf(x1, scale=sigma)

plt.figure(figsize=(8, 6))
plt.plot(x1, y1, 'b-')
plt.fill_between(x1, y1, alpha=0.2, color='blue')
plt.title('Saturation beta: HalfNormal Distribution (sigma=2)')
plt.xlabel('Saturation beta')
plt.ylabel('Probability Density')
plt.grid(True)
plt.show()

用户生成的图片

现在我们准备好训练模型并提取每个渠道的贡献了。如前所述,我们的系数存在偏差(我们知道这一点,因为每个渠道的贡献值不正确——社交媒体应该是 50%,电视应该是 35%)。然而,令人有趣的是,与我们之前运行线性回归时相比,它们现在更接近真实贡献。这实际上是营销团队的一个合理起点!

mmm_default.fit(X, y)
mmm_default.plot_waterfall_components_decomposition();

用户生成的图片

贝叶斯(自定义)先验

在我们继续之前,让我们借此机会思考一下自定义先验。我们可以做出一个(非常大胆的)假设,即每个渠道的投资回报率是相似的(或者在我们没有收入的情况下,是每销售的成本)。因此,我们可以使用渠道的支出分布来设置一些自定义先验。

由于 MMM 类在目标和特征中都进行特征缩放,因此先验也需要在缩放后的空间中提供。正如你在下面的代码中看到的那样,这实际上让我们做起来非常容易:

total_spend_per_channel = df[channel_cols].sum(axis=0)
spend_share = total_spend_per_channel / total_spend_per_channel.sum()

n_channels = df[channel_cols].shape[1]
prior_sigma = n_channels * spend_share.to_numpy()

spend_share

用户生成的图片

然后我们需要将自定义的先验传入模型中。

my_model_config = {'saturation_beta': {'dist': 'HalfNormal', 'kwargs': {'sigma': prior_sigma}}}

mmm_priors = MMM(
    model_config=my_model_config,
    adstock="geometric",
    saturation="logistic",
    date_column=date_col,
    channel_columns=channel_cols,
    control_columns=control_cols,
    adstock_max_lag=4,
    yearly_seasonality=2,
)

mmm_priors.default_model_config

用户生成的图片

当我们训练模型并提取系数时,我们看到先验已经起作用,电视现在的贡献最大(因为我们在电视上的支出超过了社交媒体)。然而,这实际上是非常错误的,说明了为什么我们在设置先验时必须如此小心!营销团队应该考虑进行一些实验来帮助他们设置先验。

mmm_priors.fit(X, y)
mmm_priors.plot_waterfall_components_decomposition();

随机预算调整

现在我们已经制定了短期计划,让我们来考虑长期计划。如果我们能够说服市场营销团队每个月对其营销渠道的预算进行小幅随机调整,是否能够创建一个没有多重共线性的数据集?

以下代码使用数据生成器函数,并模拟了一系列随机的支出调整:

np.random.seed(40)

# Define list to store results
results = []

# Loop through a range of random adjustments to spend
for spend_rand_change in np.arange(0.00, 0.05, 0.001):
    # Generate simulated data with the current spend_rand_change
    sim_data = data_generator(spend_rand_change, true_coef)

    # Run the regression
    coefficients = run_reg(sim_data, features=['demand', 'social_spend', 'tv_spend'], target='sales')

    # Store the spend_rand_change and coefficients for later plotting
    results.append({
        'spend_rand_change': spend_rand_change,
        'coef_demand': coefficients[0],
        'coef_social_spend': coefficients[1],
        'coef_tv_spend': coefficients[2]
    })

# Convert results to DataFrame for easy plotting
results_df = pd.DataFrame(results)

# Plot the coefficients as a function of spend_rand_change
plt.figure(figsize=(10, 6))
plt.plot(results_df['spend_rand_change'], results_df['coef_demand'], label='Demand Coef', color='r', marker='o')
plt.plot(results_df['spend_rand_change'], results_df['coef_social_spend'], label='Social Spend Coef', color='g', marker='o')
plt.plot(results_df['spend_rand_change'], results_df['coef_tv_spend'], label='TV Spend Coef', color='b', marker='o')

# Add lines for the true coefficients
plt.axhline(y=true_coef[0], color='r', linestyle='--', label='True Demand Coef')
plt.axhline(y=true_coef[1], color='g', linestyle='--', label='True Social Spend Coef')
plt.axhline(y=true_coef[2], color='b', linestyle='--', label='True TV Spend Coef')

plt.title('Regression Coefficients vs Spend Random Change')
plt.xlabel('Spend Random Change')
plt.ylabel('Coefficient Value')
plt.legend()
plt.grid(True)
plt.show()

从结果中我们可以看到,仅仅对每个渠道的预算进行一点小的随机调整,就能摆脱多重共线性的困扰!

用户生成的图像

值得注意的是,如果我更改随机种子(几乎就像重新抽样一样),系数的起始点会有所不同——然而,不论我使用哪个种子,系数在支出随机变化 1% 后会稳定下来。我确信这会根据你的数据生成过程而有所不同,因此一定要使用你自己的数据进行测试!

最终思考

  • 尽管本文的重点是多重共线性,但最重要的收获是模拟数据并尝试估计已知系数的重要性(记住,这些系数是你自己设置的,所以你知道它们)——如果你想对结果充满信心,这是一个必不可少的步骤!

  • 在做 MMM 时,使用你实际的支出和销售数据作为模拟的基础是很有帮助的——这将帮助你了解是否存在多重共线性问题。

  • 如果你使用实际的支出和销售数据,你也可以进行一个随机预算调整的模拟,帮助制定一个合适的随机化策略给市场营销团队。记住,我的模拟是为了阐明一个观点而简化的——我们可以设计一个更有效的策略,例如测试每个渠道响应曲线的不同区域。

  • 贝叶斯方法可能是一个陡峭的学习曲线——我们还可以采取另一种方法,即使用受限回归,在这种方法中,你根据先验知识为每个渠道系数设定上下限。

  • 如果你设置贝叶斯先验,非常重要的一点是要透明地说明它们是如何工作的,以及它们是如何被选择的。如果你选择使用渠道支出分布作为先验,那么假设每个渠道具有类似的投资回报率(ROI)需要得到相关利益相关者的批准。

  • 贝叶斯先验并不是魔法!理想情况下,你应当使用实验结果来设置你的先验——值得查看 pymc 营销如何处理这个问题:

[## Lift Test 校准 - pymc-marketing 0.8.0 文档

你可能听过这句话:“所有模型都是错的,有些模型是有用的。”这在许多领域都适用,且确实如此……

www.pymc-marketing.io

就是这样,希望你喜欢这篇文章!如果你想继续跟随我一起探索因果 AI,欢迎关注我——在下一篇文章中,我们将深入探讨不良控制的主题!

开源是实现 AI 民主化的最佳途径吗?

原文:towardsdatascience.com/is-open-source-the-best-path-towards-ai-democratization-b62a1153dcd4?source=collection_archive---------11-----------------------#2024-06-25

尽管开源模型已经使软件民主化,但将其应用于 AI 时会引发法律和伦理问题。开源 AI 运动的最终目标是什么?

Julius CerniauskasTowards Data Science Julius Cerniauskas

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

--

AI 未来的竞争刚刚遇到了一点小障碍——“开源”的定义。公众第一次听到这个术语有争议是在春初,当时 OpenAI 的联合创始人埃隆·马斯克起诉 OpenAI违反了其最初的非盈利使命(尽管几个月后他决定撤回诉讼)。

确实,OpenAI 曾长期宣传开源社区的理念。然而,这一说法受到了广泛批评,最近的一份报告显示,底层的 ChatGPT 模型是一个封闭系统,只有 API 在某种程度上保持开放。OpenAI 并非唯一一个试图加入“开源洗白”行列的科技公司——Meta 的 LLaMA 和谷歌的 BERT 也都被宣传为“开源 AI”。

不幸的是,将一个系统标榜为“开源”而实际上并非如此的问题,不仅仅是营销问题:有些情况下,标榜为“开源人工智能”可以带来法律豁免,因此企业滥用这一术语的风险是真实存在的。为了解决这一问题,开源倡议(OSI)——一个帮助创造开源软件定义的独立非营利组织——宣布将举办一系列全球研讨会,以收集各方意见,并推动开源人工智能定义达成最终一致。

尽管技术专家和开发人员在争论这一术语的范围,但现在是提出一个可能略显不舒服的问题的时候——开源运动是否是民主化人工智能、让这项技术更加透明的最佳途径?

开源软件与开源人工智能

开源软件通常指的是一种去中心化的开发过程,在这个过程中,代码公开供不同的同行进行协作和修改。OSI(开源倡议)制定了一套明确的开源定义规则,从自由再分发和非歧视到不受限制的许可。然而,有几个合理的原因说明这些原则无法轻易地移植到人工智能领域。

首先,大多数人工智能系统都是建立在庞大的训练数据集之上的,而这些数据受到不同法律体系的约束,包括版权、隐私保护、商业机密和各种保密措施。因此,开放训练数据存在法律风险。如Meta 公司人工智能研究副总裁 Joëlle Pineau所指出,目前的许可方案并不是为处理利用来自多个来源的大量数据的软件而设计的。然而,将数据保持封闭,使得人工智能系统可以公开访问,但并非开源,因为没有看到训练数据,任何人都无法利用算法架构。

第二,参与开发和部署人工智能系统的贡献者数量远远大于软件开发中的贡献者数量,后者可能只有一家企业。在人工智能的情况下,不同的贡献者可能会对人工智能系统的不同部分和输出承担责任。然而,要确定如何在不同的开源贡献者之间分配责任将是困难的。我们假设一个场景:如果基于开源模型的人工智能系统产生了误导性的输出,促使情绪困扰的人伤害自己,那么谁将为此负责?

开放性的风险

OSI 的努力基于这样的论点:要对人工智能模型进行一些修改,必须访问其底层架构、训练代码、文档、加权因子、数据预处理逻辑,以及当然,数据本身。因此,一个真正开放的系统应该允许完全自由地使用和修改系统,这意味着任何人都可以参与技术的发展。在理想的世界里,这一论点是完全正当的。然而,世界并不理想。

最近,OpenAI 已承认除非经过仔细评估所有风险,包括滥用和加速,否则他们不愿意将强大的生成性人工智能系统作为开源发布。人们可能会争论这是否是真诚的考虑,还是一项公关举措,但风险确实存在。加速是我们甚至不知道如何应对的风险——这一点在过去两年的快速人工智能发展中得到了清晰体现,令法律和政治界在众多监管问题和挑战面前感到困惑。

滥用——无论是用于犯罪还是其他目的——甚至更难以遏制。正如RAND 资助的研究所显示的那样,未来的大多数人工智能系统可能会是双重用途的,这意味着军方将会采用并改进商业开发的技术,而不是从零开始开发军事人工智能。因此,开源系统落入非民主国家和激进非国家行为者之手的风险不容忽视。

此外,还存在一些更为无形的风险,比如偏见和虚假信息的增加,这些都必须在发布开源人工智能系统时予以考虑。如果该系统可以自由修改和操作,包括更改训练数据和训练代码的可能性,那么原始的人工智能提供者几乎无法做任何事情来确保系统保持道德、值得信赖和负责任。可能正是出于这个原因,OSI 在定义其使命时明确将这些问题称为“超出范围”。因此,尽管开源可能平衡竞争环境,让小型参与者也能从人工智能创新中受益并推动其发展,但它也固有地带来了使人工智能输出变得不公平和不准确的风险。

开源模型的使用与滥用

总结来说,目前仍不清楚如何将广泛定义的开源模型应用于主要由数据构成的人工智能领域,而不会带来严重风险。开放人工智能系统将需要新的法律框架,例如负责任的人工智能许可证(RAIL),以便允许开发者防止他们的工作被不道德或不负责任地使用。

然而,这并不是说 OSI 统一定义的使命对于 AI 创新的未来不重要,而是这种重要性主要不在于推动创新和民主化,而在于确保法律的清晰性并减少潜在的操控风险。

让我们以刚刚发布的欧盟 AI 法案为例——这是首部全面的 AI 发展法规。AI 法案为开源通用人工智能(GPAI)模型提供了明确的例外,放宽了透明度和文档要求。这些模型驱动着大多数当前面向消费者的生成式 AI 产品,比如 ChatGPT。只有当模型具有“系统性风险”或是以盈利为目的时,这些豁免才不适用。

在这种情况下,更加宽松(或更加严格)的开源许可证实际上可能成为一种规避透明度和文档要求的手段,这种行为很可能是考虑到 AI 公司在不违反版权和数据隐私法的情况下,获取多维度训练数据的持续斗争。行业必须就“开源”达成一致的定义并强制执行;否则,大型公司将根据自身利益来决定“开源”的含义。

民主化数据,而非系统

尽管出于法律目的需要明确的定义,但是否广义定义的开源方法能够带来预期的技术进步并平衡竞争环境,仍然存疑。AI 系统主要建立在数据之上,获取大规模数据的难度是大科技公司的最强竞争优势之一,此外还有计算能力。

使 AI 开源并不能消除小型企业所面临的所有结构性障碍——仍然需要不断流入的数据、适当的计算能力以及高度熟练的开发人员和数据科学家来修改系统并进一步训练它。

在追求 AI 民主化的过程中,保护开放互联网和所有人都能访问的开放网络数据可能比推动开源议程更为重要。由于法律制度的冲突或过时,今天的互联网数据是碎片化的,阻碍了创新。因此,政府和监管机构必须寻找方法,重新平衡诸如版权保护等领域,使公共数据更容易获取。

ReFT 是我们所需要的一切吗?

原文:towardsdatascience.com/is-reft-all-we-needed-1ab38e457320?source=collection_archive---------6-----------------------#2024-11-21

表示微调 — 超越 PEFT 技术的 LLM 微调

Mengliu ZhaoTowards Data Science 孟柳·赵

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

--

难道大家还没有开始使用 ReFT 吗?

斯坦福大学在 2024 年 5 月发布了论文ReFT: Representation finetuning for language models,立即展示了其巨大的潜力。2024 年 7 月,Oxen.ai 展示了一项实验,在一台单独的 Nvidia A10 GPU 上,仅用了 14 分钟就完成了 Llama3(8B)的微调,进一步证明了这一技术的强大。

与 SOTA PEFT 方法不同,后者专注于修改模型权重或输入,ReFT 技术基于先前提出的分布式互换干预(DII)方法。DII 方法首先将深度学习模型的嵌入投影到一个较低维度的子空间,然后通过该子空间进行干预,以实现微调目的。

在接下来的内容中,我们将首先带领读者了解 SOTA 微调 PEFT 算法,例如 LoRA、提示微调和前缀微调;然后,我们将讨论原始的 DII 方法,为理解提供更好的背景;最后,我们将讨论 ReFT 技术,并展示论文中的实验结果。

图片来源:pxhere.com/en/photo/1377005

PEFT — 参数高效微调技术

Hugging Face 有一篇博客详细介绍了不同的 PEFT 技术,用于 LLM 的微调。在这里,我们快速回顾一下这些技术。

LoRA于 2021 年提出,因其简单性和泛化能力,已成为微调 LLMs 和扩散模型(例如,时变 LoRA)最成功的技术之一。其理念非常简单:不是微调每一层的原始权重参数,而是 LoRA 技术添加了两个低秩矩阵,仅对低秩矩阵进行微调。在微调整个网络时,训练参数可以减少到不到 0.3%,从而显著加快学习过程并减少 GPU 内存使用。

LoRA 模型更新。图片来源:arxiv.org/pdf/2106.09685

Prompt Tuning技术的提出不再改变预训练模型的内部层,而是使用“软提示”,即可学习的任务特定提示嵌入作为前缀。给定混合任务批量提示,模型可以在不增加任务特定模型副本的情况下高效地执行多任务预测(与下图中的模型微调相比)。

提示微调与传统模型微调。图片来源:arxiv.org/pdf/2104.08691

为了为大规模(例如,超过 10B 参数)的提示微调模型提供普适性,Prefix Tuning (P-Tuning v2)提出在不同层次前缀训练可学习的提示嵌入,从而允许在各个层级学习任务特定的信息。

P-tuning v2 的多尺度提示。图片来源:arxiv.org/pdf/2110.07602

在所有这些 PEFT 技术中,LoRA 因其鲁棒性和高效性而最广泛应用于 LLM 的微调。详细的实证分析可以在这篇论文中找到。

分布式干预互换(DII)

因果抽象是一种强大的人工智能框架,通过在因果模型(高层模型)和神经网络模型(或低层模型)之间进行干预,来诱导对齐估计。如果两个模型之间存在对齐关系,我们就知道因果模型和神经网络之间的潜在机制是相同的。通过干预发现潜在对齐的方法称为互换干预(II),这一概念在讲座视频中得到了直观的解释。

然而,经典的因果抽象方法使用暴力搜索通过所有可能的模型状态对齐,这种方法效率较低。分布式交换干预(DII)系统首先通过一系列正交投影将高阶和低阶模型投影到子空间,然后通过某些旋转操作生成一个干预后的模型。一个关于视觉模型的有趣干预实验可以在这里找到。

更具体地,DII 可以写成如下形式:

方程来源:arxiv.org/pdf/2404.03592

其中,R 是一个具有正交行的低秩矩阵,表示正交投影;bs是模型从两个不同输入编码出的两种不同表示;干预将在低秩空间中发生,例如包含RsRb的空间;投影矩阵R将通过分布式对齐搜索(DAS)进一步学习,该搜索旨在优化“干预后将最大化期望反事实输出概率的子空间”。

ReFT — 表示微调

因此,ReFT 技术可以被视为在低维空间中对模型隐藏表示的干预,如下所示,其中\phi 是干预,并直接应用于 L 层和 P 位置的隐藏表示:

在高层进行 ReFT 干预。图像来源:arxiv.org/pdf/2404.03592

具体来说,本文进一步提出了低秩线性子空间 ReFT(LoReFT),并进一步引入了学习到的投影源:

方程来源:arxiv.org/pdf/2404.03592

其中,h是隐藏表示,(Rs = Wh + b)是学习到的投影源,它在由R张成的低维空间中编辑表示 h。现在,我们可以在下面的原始深度神经网络层中说明 LoReFT。

图像来源:arxiv.org/pdf/2404.03592

对大规模语言模型进行微调时,语言模型的参数保持冻结,只有投影参数\phi={R, W, b}会被训练。

实验

原始论文展示了将 LoReFT(以及 ReFT 家族中的其他技术)与完整微调(FT)、LoRA、前缀调优等进行比较的实验,涵盖了四种基准测试:常识推理、算术推理、指令跟随和自然语言理解。我们可以看到,与 LoRA 相比,ReFT 技术进一步减少了至少 90%的参数,同时在性能上大幅超越。

图片来源:arxiv.org/pdf/2404.03592

讨论

为什么 ReFT 如此吸引人?首先,这项技术在 Llama 家族模型上提供了令人信服的结果,在各种基准测试中超越了当前最先进的微调方法(SOTA)。其次,这项技术深深植根于因果抽象算法中,提供了进一步的模型解释基础,尤其是从隐藏表示的角度来看。如原文所述,ReFT 表明“一个分布在一组神经元上的线性子空间可以实现对大量任务的泛化控制”,这可能为我们更好地理解大型语言模型打开新的大门。

参考文献

  • Wu Z, Arora A, Wang Z, Geiger A, Jurafsky D, Manning CD, Potts C. ReFT:语言模型的表示微调。arXiv 预印本 arXiv:2404.03592。2024 年 4 月 4 日。

  • Hu EJ, Shen Y, Wallis P, Allen-Zhu Z, Li Y, Wang S, Wang L, Chen W. LoRA:大规模语言模型的低秩适配。arXiv 预印本 arXiv:2106.09685。2021 年 6 月 17 日。

  • Zhuang Z, Zhang Y, Wang X, Lu J, Wei Y, Zhang Y. 时变 LoRA:面向扩散模型的跨领域微调。2024 年神经信息处理系统年会(The Thirty-eighth Annual Conference on Neural Information Processing Systems)。

  • Liu X, Ji K, Fu Y, Tam WL, Du Z, Yang Z, Tang J. P-tuning v2:提示调优在各个规模和任务中的普适性与微调相当。arXiv 预印本 arXiv:2110.07602。2021 年 10 月 14 日。

  • Geiger A, Wu Z, Potts C, Icard T, Goodman N. 在《因果学习与推理》2024 年 3 月 15 日(第 160–187 页)中找到可解释的因果变量与分布式神经表示之间的对齐。PMLR。

  • Lester B, Al-Rfou R, Constant N. 参数高效提示调优的规模效应。arXiv 预印本 arXiv:2104.08691。2021 年 4 月 18 日。

  • Pu G, Jain A, Yin J, Kaplan R. 对 LLM 的 PEFT 技术优缺点的实证分析。arXiv 预印本 arXiv:2304.14999。2023 年 4 月 28 日。

你的数据是在提升你,还是在拖累你?

原文:towardsdatascience.com/is-your-data-lifting-you-up-or-letting-you-down-e2fd7cc75513?source=collection_archive---------9-----------------------#2024-05-09

理解并避免数据的负面回报

Elliott StamTowards Data Science Elliott Stam

·发布于Towards Data Science ·阅读时长 12 分钟·2024 年 5 月 9 日

--

坏数据?照片来源:Giuseppe CUZZOCREAUnsplash

当我伸手去拿一个苹果时,我充满了期待:它应该是脆的、甜的、清爽的。一个好的苹果可以让我心情愉悦,脸上露出微笑。然而,如果我没有注意到这个苹果已经腐烂,那这个恶心的苹果就会让我失去笑容,并留下难闻的味道。

你的数据和你用它构建的东西与我的苹果有很多相似之处。数据可以是美味的,让你想要更多,但它也可能变坏,让你的业务感到恶心。

在合适的条件下,数据可以成为你团队的巨大资产。数据可以用来指导和帮助决策,它常常有潜力揭示有价值的机会。但要小心:从数据中获得负面回报是可能的。

没有人希望得到负面回报。这是一个糟糕的结果,尤其在当今时代,每个人都知道数据中蕴藏着价值的宝藏。没有哪个数据团队愿意承认他们带来的问题比解决的更多。他们的任务是用数据赋能业务,而不是拖慢进程。

但事实是,我们每个人都咬过烂掉的数据苹果。它们有多种表现形式:仪表盘…

您的用户基础是在增长还是在缩小?

原文:towardsdatascience.com/is-your-user-base-growing-or-shrinking-300243ca47f5?source=collection_archive---------7-----------------------#2024-09-11

如何通过追踪客户细分和关键绩效指标(KPI)揭示您业务的真实健康状况

Shirley Bao, Ph.D.Towards Data Science Shirley Bao, Ph.D.

·发布于面向数据科学 ·阅读时间:5 分钟·2024 年 9 月 11 日

--

图片由Tumisu提供,来自Pixabay

假设您站在企业仪表盘前,数字在您眼前上下波动。但这些数字能讲述整个故事吗?您的生意做得好还是仅仅是运气好,正骑着一波即将崩溃的浪潮?重点不仅仅是有多少客户在与您的业务互动,还要关注客户的类型和他们访问的频率。如果不理解全貌,您可能会轻易忽视用户基础下降的迹象,而这是任何企业都无法承受的风险。

许多企业密切关注其日活跃用户或客户(DAU/DAC)、周活跃用户或客户(WAU/WAC)或月活跃用户或客户(MAU/MAC)。DAU/DAC 衡量每天有多少独立的用户/客户与您的产品互动。这对于那些日常参与度至关重要的应用程序或服务(例如社交媒体平台)非常有用。另一方面,WAU/WAC 则关注每周活跃用户/客户的数量,这对于像杂货店或订阅服务这样的业务更为有用,因为这些业务的用户通常每周都会光顾。MAU 和 MAC 提供了更广泛的视角,显示了每月有多少独立的用户或客户参与。因此,它们...

Python 中的等时线

原文:towardsdatascience.com/isochrones-in-python-fe21814e5cb1?source=collection_archive---------9-----------------------#2024-07-29

在 Python 中突出步行可达区域

Milan JanosovTowards Data Science Milan Janosov

·发布于数据科学前沿 ·阅读时间:5 分钟·2024 年 7 月 29 日

--

在地理空间科学和位置智能中,等时线表示从特定点出发,在一定时间内可以到达的地理区域。例如,在步行距离的情况下,等时线是城市规划师等专业人士的有用工具,帮助他们了解给定区域的可达性和连通性。

通过可视化等时线,数据科学可以提供一个快速且易于使用的工具,帮助深入了解邻里之间的连通性和步行可达性,帮助识别连通性良好的区域,并指出可能需要进行基础设施改善的区域。

此外,在本文中,我们将提供一个概述,介绍如何使用 Python 包NetworkX(用于图分析)和OSMNnx(结合 OpenStreetMap 和 NetworkX)生成步行距离等时线,以布达佩斯第 12 区为例。首先,我们下载该区域的道路网络,然后选择一个随机节点(随机交叉口),接着绘制围绕该节点的 5、10、20 和 30 分钟的步行等时线。

所有图像均由作者创建。

数据获取

iTransformer:时间序列预测中的最新突破

原文:towardsdatascience.com/itransformer-the-latest-breakthrough-in-time-series-forecasting-d538ddc6c5d1?source=collection_archive---------1-----------------------#2024-04-09

了解 iTransformer 的架构,并使用 Python 在一个小实验中应用该模型。

Marco PeixeiroTowards Data Science Marco Peixeiro

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

--

图片来自David Clode,来源于Unsplash

预测领域在基础模型方面取得了很多进展,自 2024 年初以来,像 Lag-LLaMA、Time-LLM、Chronos 和 Moirai 等模型相继被提出。

然而,它们的性能略显不足(关于可重复的基准测试,见这里),我认为目前数据特定的模型仍然是最优解。

为此,Transformer 架构已经以多种形式应用于时间序列预测,其中PatchTST在长时间范围预测中达到了最先进的性能。

持挑战性的 PatchTST 迎来了iTransformer模型,该模型在 2024 年 3 月由论文iTransformer: Inverted Transformers Are Effective for Time Series Forecasting提出。

在本文中,我们发现了 iTransformer 背后引人注目的简单概念,并探讨了其架构。接着,我们在一个小实验中应用该模型,并将其表现与 TSMixer、N-HiTS 和 PatchTST 进行比较。

欲了解更多细节,请务必阅读原始论文

了解最新的时间序列…

终于是时候记住那些该死的分类指标了!

原文:towardsdatascience.com/its-time-to-finally-memorize-those-dang-classification-metrics-bdeb99e64de2?source=collection_archive---------3-----------------------#2024-06-10

这些指标背后的直觉以及我如何最终记住它们

Jarom HuletTowards Data Science Jarom Hulet

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

--

图片来自 Pexels.com,作者:cottonbro studio

古希腊哲学家苏格拉底据说不喜欢书面语言(具有讽刺意味的是,我们并不知道他具体是怎么想的,因为他从未写过任何东西 😂)——其中一个原因是,他认为书面语言让我们变得智力懒惰,因为我们可以将内容写下来,而不需要记住或内化它们。我常常想象苏格拉底会怎么想我每次使用分类性能指标时都去谷歌搜索它们!他肯定会抓狂!为了避免这种羞耻,我决定将它们记住,并且想与大家分享我的记忆工具。

在这篇文章中,我不仅会讲述我如何最终记住这些指标——我还会讲解这些指标背后的直觉(如果你不理解它们,记住它们就没有意义),也许更重要的是,每个指标所忽略的内容(请注意⚠️警告部分)。

目录

  1. 分类指标的简要概述

  2. 准确度

  3. 精确度

  4. 召回率/灵敏度

  5. 特异度

  6. F1 分数

分类指标概述

ITT 与 LATE:在非完全遵守的实验中使用 IV 估算因果效应

原文:towardsdatascience.com/itt-vs-late-estimating-causal-effects-with-iv-in-experiments-with-imperfect-compliance-7ca1220fe425?source=collection_archive---------7-----------------------#2024-10-09

直觉、逐步脚本和使用 IV 所需的假设

Robson TigreTowards Data Science Robson Tigre

·发表于 Towards Data Science ·阅读时长:9 分钟·2024 年 10 月 9 日

--

图片由作者提供

在许多实验中,并非所有被分配接受治疗的个体实际上都会接受或使用治疗。例如,一家公司可能会向客户发送折扣券,旨在让他们使用这些优惠券立即购买,这可能会增加他们未来的购买。然而,并不是所有客户都会兑换优惠券。

这一情景代表了“非完全遵守”情况(参见此处),即治疗分配并不总是能导致治疗的实施。为了估算提供优惠券对未来客户购买行为的影响,我们必须区分两种主要方法:

  • 治疗意图效应(ITT):估算被分配接受优惠券的效果,不管是否使用了优惠券。

  • 局部平均治疗效应(LATE):估算在那些遵守分配的人群中的治疗效果——那些因为被分配到接受优惠券而使用了优惠券的人。

本教程介绍了这些方法背后的直觉、假设以及如何使用 R 实现它们(参见脚本此处)。我们还将讨论用于估算 LATE 的两阶段最小二乘法(2SLS)。

直觉

在存在不完美遵循的实验中,处理分配(例如,收到优惠券)与实际使用处理(例如,使用优惠券)并不完全对应。因此,简单地将处理组与对照组进行比较可能会导致误导性的结论,因为那些确实使用处理的个体(下图中的蓝色组)的治疗效果会在更大的处理组(绿色组)中被稀释。

图片由作者提供

为了解决这种情况,我们使用了两种主要方法:

意向处理(ITT)

它衡量的是分配到某一处理的效果,无论个体是否真正执行该处理。在我们的例子中,它比较了被分配到接收优惠券(处理组)的顾客与没有接收优惠券(对照组)的顾客未来的平均购买行为。这种方法对于理解分配本身的效果非常有用,但它可能低估了处理的影响,因为它包括了那些没有使用优惠券的个体。

图片由作者提供

局部平均处理效应(LATE)

在这里,我们使用工具变量(IV)方法来估计局部平均处理效应(LATE),即处理对于那些遵守分配的个体(“遵守者”)的因果效应——即那些因为被分配接收优惠券而使用优惠券的人。总结如下:

  • 随机分配到处理(收到优惠券)作为工具变量,强烈预测实际的处理采纳(使用优惠券)。

  • 工具变量(IV)必须满足特定的假设(相关性、外生性和排除限制),我们将详细讨论这些假设。

  • 工具变量提取了优惠券使用中由随机分配所引起的变异部分,消除了可能偏倚估计的未观察因素的影响(了解更多关于“选择偏差”的内容)。

  • LATE 通过调整处理分配(ITT)的影响和遵循率(在顾客被分配的情况下使用优惠券的概率),来估计处理效果。

  • 它通过二阶段最小二乘法(2SLS)估计,其中每个阶段在下图中进行了说明。该方法的直观解释在第五部分中进行了讨论。

图片由作者提供

假设和局限性

虽然可以通过使用 OLS 直接获得 ITT 估计值,但 IV 方法需要强假设才能提供有效的因果估计。幸运的是,在实验情境中这些假设通常是成立的:

工具相关性

工具变量(在此案例中,是被分配到处理组)必须与我们想要衡量其对未来购买影响的内生变量(优惠券使用)相关。换句话说,随机分配接收优惠券应该显著增加顾客使用优惠券的可能性。这可以通过第一阶段回归中处理组系数的大小和统计显著性来检验。

工具变量的外生性和排除限制

工具变量必须与任何未观察到的影响结果(未来购买)的因素独立。它应当仅通过其对内生变量(优惠券使用)的影响来影响结果。

简单来说,工具变量应该仅通过影响优惠券使用来影响结果,而不是通过任何其他途径。

在我们的场景中,优惠券的随机分配确保其与任何可能影响未来购买的未观察到的顾客特征没有相关性。随机化还意味着,被分配优惠券的影响主要取决于顾客是否选择使用它。

局限性与挑战

  1. LATE 只提供“符合条件者”的因果效应——即那些因为收到优惠券而使用优惠券的顾客,这一效应仅适用于这一群体(仅局部有效)。它不能推广到所有顾客或因其他原因使用优惠券的顾客。

  2. 当符合条件的顾客比例较低时(即只有一小部分顾客对处理作出反应),估计的效应变得不太精确,研究结果的可靠性较低。由于效应基于少数符合条件的顾客,因此也很难确定这些结果是否对更广泛的群体有意义。

  3. 外生性假设和排除限制并不能直接进行检验,这意味着我们必须依赖实验设计或理论论证来支持 IV 实施的有效性。

使用 R 进行工具变量实操

现在我们理解了直觉和假设,我们将通过一个例子应用这些技术,在 R 中估计 ITT 和 LATE。我们将探讨以下场景,并在这个R 脚本中进行了复现:

一家电子商务公司希望评估使用折扣优惠券是否能增加顾客未来的购买量。为了避免选择偏差,优惠券被随机发送给一组顾客,但并非所有收到优惠券的顾客都使用了它。此外,未收到优惠券的顾客无法使用优惠券。

图片由作者提供

我模拟了一个数据集来表示这种情况:

  • 处理组: 一半顾客被随机分配接收优惠券(处理组 = 1),另一半顾客则没有接收(处理组 = 0)。

  • 优惠券使用: 在接受处理的个体中,那些使用了优惠券进行购买的人通过 coupon_use = 1 进行标识。

  • 收入和年龄: 模拟的协变量,遵循正态分布。

  • 优惠券使用概率: 为了使模型更现实,优惠券使用的概率在接受优惠券的人群中有所不同。收入较高、年龄较低的个体更倾向于使用优惠券。

  • 未来购买: 结果变量——未来购买(以巴西雷亚尔为单位),也受到收入和年龄的影响。

  • 过去购买: 之前几个月的购买金额(以巴西雷亚尔为单位),在优惠券分配之前。这不应与控制协变量后的优惠券使用或接收存在相关性。

  • 最后,对于那些使用了优惠券的客户,模拟的优惠券使用效应设置为“true_effect <- 50”。这意味着,平均而言,使用优惠券将使未来购买增加 50 巴西雷亚尔。

验证假设

工具变量的相关性: 第一阶段回归解释了属于处理组与使用优惠券之间的关系。在该回归中,“treatment”的系数为 0.362,意味着约 36%的处理组使用了优惠券。该系数的 p 值为< 0.01,t 值为 81.2(显著),表明处理分配(接收优惠券)显著影响优惠券的使用。

作者提供的图片

工具变量外生性和排除限制: 从构造上讲,由于分配是随机的,因此工具变量与影响未来购买的未观察到的因素不相关。但无论如何,这些假设可以通过以下两个结果集间接进行检验:

第一个结果集包括来自第一阶段(仅在脚本中)和第二阶段(见下)的回归结果,有和没有协变量。这些结果应当得出相似的结论,以支持我们的工具变量(优惠券分配)仅通过内生变量(优惠券使用)影响结果(未来购买)的观点。在没有协变量的情况下,估计效应为 49.24,p 值 < 0.01;在有协变量的情况下,估计效应为 49.31,p 值 < 0.01。

作者提供的图片

第二个结果集包含了一项安慰剂检验,用于确定工具变量是否会影响过去的购买(从逻辑上讲,它不应影响)。该检验表明,工具变量除了通过内生变量的效应外,不会直接影响结果。无论是否包含协变量,估计效应都接近零且不具有统计显著性。

作者提供的图片

通过两阶段最小二乘法(2SLS)估计 LATE

由于上述间接测试的性质,我们最终提前预见了主要分析的结果,包括 LATE(见“小节“验证假设”中的第二张图”)。但现在让我们更详细地讨论这个估计过程,它包括使用 2SLS 方法的两个步骤:

  1. 第一阶段:我们将优惠券使用情况与处理分配进行回归。这会生成优惠券使用的预测值,隔离出归因于随机分配的部分。

  2. 第二阶段:我们将第一阶段预测的优惠券使用情况与未来购买行为进行回归。这一步骤使我们能够估计优惠券使用的因果影响。

first_stage <- lm(coupon_use ~ treatment + income + age, data = data)
second_stage <- ivreg(future_purchases ~ coupon_use + income + age | treatment + income + age, data = data)

通过应用 2SLS 方法,我们得出了一个无偏的因果效应估计,专门针对那些遵守治疗方案的顾客,有效地过滤了随机分配的影响。

估计因果效应

ITT 估计:它衡量了提供优惠券对顾客未来购买行为的平均影响。在我们具体的例子中,处理组的系数为 R$17.89,p 值小于 0.01,同时控制了年龄和收入。这表明提供优惠券有显著影响,尽管由于不符合要求,ITT 通常会低于真实的因果效应。

图片来源:作者

使用 IV 的 LATE 估计:它代表了在符合条件者(那些因为收到优惠券而使用它的顾客)中,优惠券使用对未来购买行为的因果影响。在我们的例子中,估计的 LATE 接近$50(见“小节‘验证假设’中的第二张图”),这表明那些遵守使用优惠券的顾客,相较于没有优惠券的情况,他们的未来购买行为增加了大约$50。

这个结果大于 ITT 效应 R$17.89,因为 LATE 特别关注符合条件的顾客,他们更有可能体验到使用优惠券的真实因果影响。现在你可以看到,在这种实验中,衡量 LATE 有多么重要,它帮助我们更准确地理解那些真正使用治疗方法的个体的影响,并提供关于干预真正效果的洞察。

出于好奇,运行以下部分的脚本,你将会发现,符合条件者和不符合条件者之间的均值差异,或者符合条件者和对照组之间的差异,并不能给我们模拟数据中的 LATE——这是许多数据专业人士往往忽视的一点。

mean_compliers - mean_non_compliers # difference in means between compliers and non-compliers
mean_compliers - mean_control # difference in means between compliers and control

需要牢记的洞察

许多数据专业人士只关注 ITT 估计,通常是因为它更容易解释,或因为他们不熟悉其他估计量。然而,ITT 效应可能掩盖了对那些实际“接受”处理的个体的真实影响。另一方面,LATE 提供了一个更准确的干预效果衡量,特别是在符合条件者中,提供了 ITT 单独无法捕捉的洞察。

尽管工具变量(IV)方法一开始可能显得复杂,但在实验设计中,随机分配使得 IV 成为一种强大且可靠的工具,用于孤立因果效应。然而需要注意的是,IV 方法也可以应用于实验环境以外的情境,只要假设成立。例如,在模糊回归不连续设计(RDD)中,IV 是估计局部处理效应的可信方法,尽管尖锐 RDD可能看起来更直接。

感谢阅读。关注我,获取更多系列内容 😃

如果你喜欢这篇内容,并希望深入了解因果推断和计量经济学,可以在这里Linkedin关注我,我会在这两个平台上发布关于因果推断和职业发展的内容。

你想支持我吗?只需与可能感兴趣的人分享这个内容!

推荐参考文献

  • Facure M.(2022)。《勇敢与真实的因果推断》,第八章第九章

  • Huntington-Klein N.(2021)。《效应:研究设计与因果关系导论》,第十九章

我做过 80 多次数据科学面试——以下是有效的方法

原文:towardsdatascience.com/ive-done-80-data-science-interviews-here-s-what-works-ae8053f79a6d?source=collection_archive---------2-----------------------#2024-12-24

3 年的数据科学面试经验

Egor HowellTowards Data Science Egor Howell

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

--

图片由 Sebastian Herrmann 提供,来源于 Unsplash

我已经进行过超过 30 次数据科学面试,也亲自面试过 50 多次,所以我想从两方的角度给出我最好的建议。

面试是一个数字游戏

在五个地方面试,你获得工作邀请的机会要比只在一个地方面试高得多。你不需要是天才就能明白这一点。

我常常告诉人们,面试和求职申请是一个数字游戏,因为有很多你无法控制的因素可能会出错,例如:

  • 你可能是一个很好的候选人,但也许有一个人更适合这个角色。

  • 面试官那天状态不好,你们之间没有默契。

  • 他们问了你最不会的那个问题。

  • 你那天很累,身体不舒服,等等,这些因素影响了你的表现。

我雇佣了三批数据科学实习生——这是我对获得实习机会的建议

原文:towardsdatascience.com/ive-hired-3-cohorts-of-data-science-interns-here-s-my-advice-on-getting-an-offer-036bdba7d4b2?source=collection_archive---------1-----------------------#2024-09-24

获取数据科学实习机会的简历和面试技巧

Jarom HuletTowards Data Science Jarom Hulet

·发表于 Towards Data Science ·阅读时长 13 分钟·2024 年 9 月 24 日

--

图片来源:Djordje Samkov,来自 Pexels.com

在我目前的工作中,我负责审阅简历、进行面试,并做出数据科学实习生的聘用决策,已经有三年时间了。随着我的团队为第四批夏季实习生做准备,我认为基于我的经验和观察,发布一些建议可能会对你有所帮助。

这不是一本关于如何获得数据科学实习的全面指南——很多其他人已经在这个话题上做了很好的工作。这是一个集合了我个人经验的建议,来源于我审阅了数百份简历并进行了几十次数据科学实习面试。我的希望是,你能从中找到一些独特的建议,帮助你完善简历和面试技巧。我将这些建议按我认为的重要性排序。让我们开始吧!

建议 1:差异化始终很重要,但对于实习来说尤其重要!

当你寻找任何类型的工作时,你总是希望能够区分自己和你的简历。这通常通过工作经验自然发生。但对于实习来说,工作经验并不常见…

Jamba:全新的混合型变换器/马姆巴模型

原文:towardsdatascience.com/jamba-the-new-hybrid-transformer-mamba-f18ee6ce0768?source=collection_archive---------10-----------------------#2024-04-30

比变换器更快、更好,但训练起来更为困难

Benjamin MarieTowards Data Science Benjamin Marie

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

--

使用 DALL-E 生成

变换器神经网络架构是最先进的。它扩展性极好,即更大的模型能学得更好,并且由于注意力机制的并行计算,训练起来也很高效。

然而,变换器也有一些缺点,尤其是在推理阶段。注意力的计算成本随着序列长度的增加而呈二次方增长。为了缓解这一成本,已经提出了许多技术,比如 Alibi 和 RoPE。

也有一些替代的神经网络架构被提出,比如 RWKV 和 Mamba,它们是无注意力机制的状态空间模型(SSM)。与变换器相比,它们在推理时效率更高,但在准确性方面仍有所欠缺。

为了同时利用变换器和 SSM 架构,提出了 Jamba。这个混合模型结合了 SSM 和变换器层。这种结合使得在内存使用、训练效率和长上下文能力之间实现了平衡。

Jamba: 一种混合变换器-马姆巴语言模型

Jamba 的表现与 Mixtral-7x8B(最好的开放式大型语言模型之一)相当,但它更高效,特别是在处理长上下文时。

一月是挑战自我学习新技能的时刻

原文:towardsdatascience.com/january-is-for-challenging-yourself-to-learn-new-skills-812a66d0cd88?source=collection_archive---------7-----------------------#2024-01-11

TDS 编辑Towards Data Science TDS 编辑

·发布于 Towards Data Science ·发送至 Newsletter ·3 分钟阅读·2024 年 1 月 11 日

--

你可以随时开始你的数据科学之旅;扩展你的技能应是一个持续进行的、全年的过程。不过,即便是那些对新年决心持怀疑态度的人,也无法否认新的一年所带来的激动与机遇。何不趁此时机,勇敢尝试并探索新课题呢?

为了给你提供一些有益的推动,我们整理了最近几周的精彩文章,重点介绍了机器学习和数据工作流的实用、易懂的方式。这些文章大多数对初学者友好,但正如我们常常提醒自己的:当你决定学习新事物时,你永远都是初学者。

我们希望你喜欢本周的精选内容,并能激励你在全年迎接新的挑战。让我们开始吧。

  • 学习机器学习的勇气:梯度下降与常用优化器的详细探索 在她的系列机器学习讲解新篇章中,Amy Ma 提供了一个详尽且易于理解的梯度下降和其他优化器指南,重点介绍了根据你要完成的任务选择合适的优化器。

  • 从 Adaline 到多层神经网络如果你觉得在阅读机器学习论文中的那些复杂数学符号时不太踏实,Pan Cretan最近的深度解析是一个很好的资源。它回顾了多层神经网络的早期发展,从零开始构建一个,并解读了这些网络的数学描述。

  • 高斯喷溅的全面概述如果你是一个喜欢紧跟最新研究的高级从业者,Kate Yurkova的高斯喷溅入门是必读之作。它是探索这一新兴的 3D 表示方法及其各种实际应用场景的理想起点。

Jeff Sheldon 拍摄,图片来源于 Unsplash

  • LLM 代理 — 直观且详尽的解析准备好挽起袖子,开始动手尝试大语言模型了吗?Daniel Warfield最近分享了一个详尽的、集中的资源,介绍了 LLM 代理的工作原理,以及它们如何帮助你优化与 AI 工具的互动。

  • 线性代数的鸟瞰图:线性方程组、线性回归与神经网络增强你的数学基础或刷新那些你只是略微了解过或很久以前学过的概念,永远不会嫌晚。Rohit Pandey在他关于线性代数的系列文章中,最新的一篇深入探讨了线性方程组的求解方法。

还感觉充满活力吗?我们希望是的——以下是我们本周想要与您分享的其他一些重要贡献:

  • 深入探讨参数效率问题,Mariano Kamp提供了关于 LoRA (低秩适配)设计决策的详细分析,帮助你最大化 LoRA 的使用效果。

  • 无论你是数据团队负责人还是个人贡献者,阅读Ella Pham关于实现可衡量 ROI 的挑战的新文章,你都会受益匪浅——文章还探讨了如何克服这些挑战。

  • 在大规模实施生成性 AI 解决方案时,区分炒作与真实价值至关重要。Barr Moses分享了五个技术领导者应牢记的重要见解。

  • 是否可以将 AI 工具作为求职候选人的一部分? 它应如何影响招聘经理对申请人的评估?Christine Egan深思熟虑地剖析了这一棘手话题的利害关系。

  • 想了解 LLM 在过去一年中的进展以及 2024 年该领域的未来方向,不要错过Zhaocheng Zhu的精彩文章(与共同作者Michael Galkin等人合作),该文深入探讨了模型的推理能力。

  • 将一些 Python 代码、数据可视化和 MIDI 文件结合起来(!),Dmitrii Eliuseev试图回答一个看似简单但实际上并不简单的问题:弹钢琴到底需要多少个键?

感谢你支持我们作者的工作!如果你喜欢在 TDS 阅读的文章,考虑成为 Medium 朋友会员:这是一个新加入的会员级别,能够为你最喜欢的作者提供更丰厚的回报,奖励他们的高质量写作。

Jet Sweep:优化路线以访问每支 NFL 球队的主场

原文:towardsdatascience.com/jet-sweep-route-optimization-to-visit-every-nfl-team-at-home-9a43e0bc4810?source=collection_archive---------3-----------------------#2024-06-03

用线性规划解决旅行商问题

Sejal DuaTowards Data Science Sejal Dua

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

--

图片来源:Brendan BealeUnsplash

大多数体育行业人士或狂热粉丝都曾想过,“一生中能访问每一个 NFL 体育场、NBA 体育馆或 MLB 棒球场不是很酷吗?” 虽然从我现在的位置来看,这个目标似乎遥不可及,但我足够关注篮球、棒球和美式足球,时不时地会想起这个愿望清单上的项目。我最近还对各种调度和路线优化问题产生了浓厚的兴趣。因此,我为你呈现一个体育迷的梦想假期。随着 2024–25 赛季 NFL 赛程的发布,你正在计划一次旅行,旨在以最优路线访问每一个 NFL 体育场,参加主场比赛。

NFL 相关背景

从零开始解决这个问题时,我们需要获取一些初步信息:

(1) 每个 NFL 体育场的纬度和经度坐标。

(2) 即将到来的 NFL 赛程,包括日期和主场地点。

体育场详情与距离矩阵

我写了一个简单的 Python 脚本,从维基百科中获取每个 NFL 体育场的纬度和经度信息。

《铃儿响叮当与统计检验》

原文:towardsdatascience.com/jingle-bells-and-statistical-tests-33ea90912099?source=collection_archive---------5-----------------------#2024-12-25

数据类型、假设及适用于它们的统计检验,通过节日圣诞市场示例来解释🎄🎅🎡

Gizem KayaTowards Data Science Gizem Kaya

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

--

照片由Humphrey Muleba提供,来源于Unsplash

这是一年中那段神奇的时光。闪烁的灯光和闪亮的装饰品让人眼前一亮;而礼物、笑声、家庭时光和热腾腾的红酒温暖着心灵。尽管寒冷的冬天依旧肆虐,身处人群中,和大家一起享受这些美妙的时刻,依然带来一份温馨的喜悦。

这真的是一种工作危害——连续三天在圣诞市场游荡,我不禁开始从统计分析的角度看待一切。然后我恍然大悟,为什么不通过圣诞节的可爱例子来解释统计检验,让它们更加有趣且容易理解呢?祝所有庆祝的人们圣诞快乐,充满爱、笑声,当然还有热红酒(glühwein)。祝阅读愉快!

让我们先来回顾一下统计检验到底是什么。它们是用于对数据进行推断的基本工具。它有点像是试图预测圣诞市场的人群规模——我们提出一个假设,并进行检验,看看我们是否正确(或者完全错了!)。我们提出一个关于研究问题的陈述——一个假设,并使用适当的技术——统计检验——来接受或拒绝它…

使用 AI 代理自动化职位搜索

原文:towardsdatascience.com/job-search-2-0-turbo-579e1bdb5177?source=collection_archive---------4-----------------------#2024-05-03

逐步指南:构建一个 AI 代理团队,自动化并优化职位搜索和筛选过程,将求职者的技能与职位要求匹配

Hussein JundiTowards Data Science Hussein Jundi

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

--

使用 AI 代理的职位搜索 — 作者提供的图像

用户:AI 找到最适合我的职位。

AI:正在处理..

目录

介绍

  1. 影响每个求职者的常见挑战(为什么..)

  2. AI 代理在优化职位搜索过程中的关键角色(什么..)

  3. 将 AI 代理能力引入过程(如何做..)

动手实践:构建 AI 驱动的职位搜索引擎

  1. 项目结构

  2. 框架

  3. 工具

  4. 数据

  5. 任务

  6. 代理

  7. 大型语言模型

  8. 输出模型

  9. 整合一切:团队

  10. 结果分析

共同学习奖励与策略:一个迭代的反向强化学习框架,带有排名合成轨迹

原文:towardsdatascience.com/jointly-learning-rewards-and-policies-an-iterative-inverse-reinforcement-learning-framework-with-ecf52909e5ef?source=collection_archive---------3-----------------------#2024-11-10

一种新颖的可解且可解释的算法,用于从专家演示中学习

Hussein FellahiTowards Data Science Hussein Fellahi

·发表于 Towards Data Science ·阅读时间 12 分钟·2024 年 11 月 10 日

--

图片由Andrea De Santis提供,来源于Unsplash

介绍

模仿学习(Imitation Learning)近年来在机器学习社区中获得了越来越多的关注,因为它能够通过观察到的行为将专家的知识传递给自主体。一类算法是行为克隆(BC),其目标是直接复制专家的演示,将模仿过程视为一种监督学习任务,其中自主体试图在给定状态下匹配专家的行为。虽然 BC 方法直观且计算高效,但通常会遭遇过拟合和较差的泛化能力问题。

相比之下,反向强化学习(IRL)的目标是通过推断奖励函数来揭示专家行为背后的潜在意图,这个奖励函数可以解释专家在特定环境下的行为为何是最优的。然而,IRL 的一个重要警告是该问题本质上是不良条件的——即存在多个(如果不是可能有无限多个)奖励函数可以使专家的轨迹看起来是最优的。一类广泛采用的方法来解决这种模糊性包括最大熵 IRL 算法,这些算法引入了熵最大化项,以鼓励推断策略中的随机性和鲁棒性。

在本文中,我们选择了一条不同的道路,提出了一种新的迭代式逆强化学习(IRL)算法,该算法仅通过专家演示共同学习奖励函数最优策略。通过迭代合成轨迹并保证其质量不断提高,我们的方法与传统的 IRL 模型有所不同,提供了一种完全可处理、可解释且高效的解决方案。

文章的组织结构如下:第一部分介绍了逆强化学习(IRL)中的一些基本概念。第二部分概述了我们模型所基于的 IRL 文献中的最新进展。第三部分推导了我们模型收敛的充分条件。这个理论结果是通用的,可以应用于一类算法。第四部分我们正式介绍了完整的模型,最后在第五部分总结了与现有文献的主要差异并提出了进一步的研究方向。

1. 背景定义:

首先定义几个概念,从一般的逆强化学习问题开始(注意:我们假设与本文使用相同的符号):

An 逆强化学习(IRL)问题 是一个五元组 (S, A, P, γ, τ),其中:*

  • S 是 代理可以处于的状态集合

  • A 是 代理可以采取的行动集合

  • P 是 转移概率

  • γ ∈ (0, 1] 是一个 折扣因子

  • τ 是专家演示的集合,即 τᵢ = (sᵢ, aᵢ) 是专家代理采取的行动序列(有时称为轨迹)*

逆强化学习的目标是 推断奖励函数 R ,该函数是从专家轨迹中仅通过 MDP (S, A, R, P, γ) 获得的。假设专家对这个奖励函数有完全的了解,并以最大化其行动奖励的方式进行操作。

我们还做了一个附加假设,即奖励函数的线性性(在 IRL 文献中很常见),即它的形式为:

其中 ϕ 是状态-动作空间的静态特征映射,w 是权重向量。实际上,这个特征映射可以通过经典的机器学习方法(例如 VAE — 请参见[6]中的示例)找到。因此,特征映射可以单独估计,这将 IRL 问题简化为推断权重向量 w,而不是完整的奖励函数。

在此背景下,我们最终推导出特征期望 μ,这将在不同的方法中证明是有用的。首先从给定策略 π 的值函数开始:

然后我们使用上述引入的奖励函数的线性假设:

同样,μ 也可以单独计算 —— 通常通过蒙特卡罗方法。

2. 相关工作在 RL 和 IRL 文献中

2.1 学徒学习:

从专家演示中学习的一个开创性方法是学徒学习法,首次在[1]中介绍。与纯反向强化学习不同,这里的目标是同时找到最优奖励向量以及从给定的演示中推断出专家策略。我们从以下观察开始:

从数学上看,这可以通过柯西-施瓦茨不等式来理解。这个结果实际上非常强大,因为它使我们能够专注于匹配特征期望,这将保证价值函数的匹配 — 不管奖励权重向量如何

在实践中,学徒学习法使用基于最大边际原则的迭代算法来逼近μ(π)* — 其中π是(未知的)专家策略。具体步骤如下:

  • 从一个(可能是随机的)初始策略开始,计算其特征期望,并计算专家策略的特征期望,这些专家策略来自演示(通过蒙特卡罗方法估计)

  • 对于给定的特征期望,找到能最大化μ(π)和其他μ(π)*之间边际的权重向量。换句话说,我们希望找到一个权重向量,尽可能区分专家策略和训练策略

  • 一旦找到这个权重向量w’,使用经典的强化学习 — 使用特征映射ϕw’来近似奖励函数 — 来找到下一个训练好的策略

  • 重复前两步,直到μ(π)与任何给定策略μ(π)*之间的最小边际低于某个阈值 — 这意味着在所有训练过的策略中,我们找到了一个在一定ϵ范围内与专家特征期望匹配的策略

更正式地写法:

来源:机器人自主原理 II,第 10 讲 ([2])

2.2 带排序的演示的 IRL:

学徒学习中的最大边际原则不对不同轨迹之间的关系做任何假设:当任意一组轨迹达到足够窄的边际时,算法就会停止。然而,演示的亚最优性是反向强化学习中的一个广为人知的问题,尤其是在演示质量的方差上。我们可以利用的附加信息是演示的排序 — 进而对特征期望进行排序。

更精确地说,考虑排名{1, …, k}(从最差到最好)和特征期望μ₁, …, μₖ。特征期望μᵢ是从排名为i的轨迹中计算得出的。我们希望我们的奖励函数有效地区分不同质量的演示,即:

在这个背景下,[5]提出了一个可处理的问题公式化,转化为一个二次规划(QP),再次使用最大边际原则,即最大化两个不同类别之间的最小边际。形式上:

这实际上与 SVM 模型用于多类分类的优化非常相似。整体优化模型如下——详细内容见[5]:

来源:[5]

2.3 基于扰动的奖励外推(D-REX):

在[4]中提出的 D-REX 算法也使用了这个带排名偏好的 IRL 概念,但应用于生成的示范。直觉如下:

  • 从专家示范开始,通过行为克隆模仿它们,从而得到一个基准策略π₀

  • 通过注入不同噪声级别π₀中,生成不同表现度的示范排名集合:在[4]中,作者证明了对于两个噪声级别ϵγ,当ϵ > γ(即ϵ比γ“更嘈杂”)时,我们有很高的概率得到V[π(. | ϵ)] < V[π’. | γ)]-,其中π(. | x)是注入噪声xπ₀中的策略。

  • 给定提供的自动排名,运行基于排名示范的 IRL 方法(T-REX),该方法通过使用带对偶损失的神经网络训练来近似奖励函数——更多细节见[3]。

  • 利用从 IRL 步骤中得到的奖励函数R’的近似,使用R’运行一个经典的强化学习方法,以获得最终策略。

更正式地说:

来源:[4]

在[4]中提出的另一个重要理论结果是排名对奖励模糊性的影响:论文设法量化了因增加排名约束而导致的模糊性减少,这优雅地解决了逆强化学习(IRL)中不良问题的本质。

2.4 引导强化学习:

如何在拟合强化学习模型时利用一些专家示范?与其从一个初始的随机策略开始探索,不如考虑利用可用的示范信息——即使它们可能是次优的——作为热启动,至少引导强化学习训练的开始。这个想法在[8]中得到了形式化,直觉是:

  • 对于每次训练迭代,从专家策略/示范(πᵍ)开始——收集“好”的样本,并将智能体置于“好”的状态中。

  • 在一个确定的切换点之后,让当前训练的策略(πᵉ)接管并探索状态——目标是让它探索专家示范中未曾访问(足够)过的状态,同时依赖专家对已访问状态的选择。

  • 随着进步,πᵉ应该更早接管。

更正式地说:

来源:[8]

3. 收敛的充分条件:

在推导完整模型之前,我们建立以下结果,它将提供一个有用的边界 保证迭代算法中的改进 —— 完整证明见附录:

定理 1: 设 (S, A, P, γ, π) 为具有未知真实奖励函数 R* 的逆强化学习问题。对于使用候选奖励函数 R₁ 和 R₂ 拟合的两个策略 π₁ 和 π₂,形式为 Rᵢ = R* + ϵᵢ,其中 ϵᵢ 是某种误差函数,我们有以下充分条件使得 π₂ 改进 π₁,即 V(π₂, R) > V(π₁, R):*

其中 TV(π₂, π₁) 是 π₂ 和 π₁ 之间的总变差距离,将策略解释为概率度量。

该边界提供了一些直观的见解,因为如果我们想要保证在已知策略及其奖励函数上取得改进,边际会随着以下情况增加:

  • 两个奖励函数的不同程度

  • 两个策略的不同程度

  • 原始奖励函数的不精确性

4. 完整的可处理模型

基于前面介绍的模型和定理 1,我们可以推导出我们的新完全可处理模型。直觉是:

  • 初始化: 从专家轨迹 )* 开始,估计初始策略 π₀(例如,通过行为克隆),并生成一个代理人跟随 π₀ 的轨迹 (τ₀)。利用这些轨迹估计奖励权重向量 w₀(使得 R₀ = w₀ ϕ )通过第 2.2 节中展示的二次规划来满足 V(π, R₀) > V(π₀, R₀)* —— 即此处的排名为:{2 : τ, 1: τ₀}*

  • 迭代: 使用 wᵢ-₁ 推断策略 πᵢ(即 Rᵢ = wᵢ-₁ϕ),使得我们有 V(πᵢ, Rᵢ) > V(πᵢ-₁, Rᵢ-₁) 具有足够的边际,使用引导强化学习。请注意,这里 我们不一定希望 πᵢ 相对于 Rᵢ 是最优的,我们只是希望某个策略能以定理 1 所规定的边际超过当前策略。其背后的原因是我们不希望在 奖励错误指定上过拟合。有关此误指定影响的更多细节,请参见 [7]。

  • 同样,生成样本 τᵢ 通过策略 πᵢ 并使用可处理的 IRL 模型对以下更新后的排名进行处理:{i : τ, i-1: τᵢ, i-2: τᵢ-₁…}*

  • 停止条件:V(τ, wᵢ) — V(τᵢ, wᵢ)* 低于某个阈值 ϵ 或二次规划 不可行

严格来说:

该算法做出了一些选择,我们需要牢记:

  • 我们做出的一个隐含假设是奖励函数在迭代过程中变得更加精确,即 噪声项 ϵᵢ 的范数逐渐减小并趋向于 0

  • 由于噪声项 ϵᵢ 是未知的,我们将其替换为 预定的边际调度 (mᵢ) —— 根据上述假设,我们可以让该调度递减并趋向于 0

  • 为什么在 QP 不可行时停止迭代:我们知道 QP 在上一次迭代时是可行的,因此使其不可行的主要约束是添加了使用πᵢ的轨迹计算的新特征期望μᵢ。我们将这种不可行性解释为我们无法获得足够显著的边际以区分μᵢ和μ,这可能意味着* wᵢ-₁πᵢ*是最优解。

5. 结论和未来工作:

在综合 RL 和 IRL 文献中的多个模型时,这种新的启发式方法在许多方面进行了创新:

  • 完全可处理性:不使用神经网络进行 IRL 步骤是一种选择,因为它提高了可处理性和可解释性。它还提供了与QP 可行性相关的额外见解,可以让算法更早地停止。

  • 效率和易用性:尽管是一个迭代算法,但每一步的迭代都可以非常高效地完成:QP 可以使用当前的求解器快速求解,RL 步骤只需要有限的迭代次数即可满足定理 1 中提出的改进条件。它还提供了额外的灵活性,通过ϵ系数,允许用户在需要时“付出”一些次优性以加速收敛。

  • 更好地利用所有可用信息以最小化噪声:该算法的迭代特性限制了从 IRL 到 RL 的不确定性传播,因为我们总是重新开始拟合(提供了修正的可能性)。引导 RL 步骤还允许引入对专家示范的健康偏向

我们还可以注意到定理 1 是一个一般性质,并提供了一个可以应用于大类算法的界限。

进一步的研究自然可以用于扩展该算法。首先,对该算法的实现和基准测试与其他方法进行对比,可以提供有趣的见解。另一个方向是深入研究模型的收敛条件,特别是奖励噪声减少的假设。

附录:

我们通过类似于[4]中定理 1 的证明,证明了前面引入的定理 1。对于在步骤ii-1拟合的两个给定策略,目标不等式为:V(πᵢ, R) > V(πᵢ-₁, R)。目标是推导出这个不等式成立的充分条件。我们从以下假设开始:

  • π₁是使用奖励函数R₁在上一次迭代中拟合的策略。

  • RL 步骤使用一种迭代算法形式(例如,值迭代),这意味着在当前迭代中π₂也是已知的——即使它只是一个候选策略,如果不符合条件,仍然可以改进。它正在拟合的奖励函数是R₂

  • 奖励函数 Rᵢ的形式为:Rᵢ = R + ϵᵢ,其中ϵᵢ*是某种误差函数。

因此,我们得出:

根据所做的假设,V(π₂, R₂) — V(π₁, R₁) 在迭代时已知。对于表达式的第二部分,其中涉及ϵ₁ϵ₂(这是未知的,因为我们只知道ϵ₁ — ϵ₂ = R₁ — R₂),我们推导出其值的上界:

其中TV(π₁, π₂)π₁π₂之间的总方差,因为我们将策略解释为概率测量。我们将这个上界重新代入第一个表达式,得到:

因此,这给出了以下条件,以确保策略π₂优于π₁

参考文献:

[1] P. Abbeel, A. Y. Ng, 通过逆向强化学习进行学徒学习 (2004), 斯坦福人工智能实验室

[2] J. Bohg, M. Pavone, D. Sadigh, 机器人自主原理 II (2024), 斯坦福 ASL 网站

[3] D. S. Brown, W. Goo, P. Nagarajan, S. Niekum, 通过观察进行逆向强化学习,超越次优示范 (2019), 机器学习研究会论文集

[4] D. S. Brown, W. Goo, P. Nagarajan, S. Niekum, 通过自动排名示范进行优于示范者的模仿学习 (2020), 机器学习研究会论文集

[5] P. S. Castro, S. Li, D. Zhang, 多专家排名逆向强化学习 (2019), arXiv

[6] A. Mandyam, D. Li, D. Cai, A. Jones, B. E. Engelhardt, 核密度贝叶斯逆向强化学习 (2024), arXiv

[7] A. Pan, K. Bhatia, J. Steinhardt, 奖励误设定的影响:映射与缓解模型错位 (2022), arXiv

[8] I. Uchendu, T. Xiao, Y. Lu, B. Zhu, M. Yan, J. Simon, M. Bennice, Ch. Fu, C. Ma, J. Jiao, S. Levine, K. Hausman, 跳跃启动强化学习 (2023), 机器学习研究会论文集

一个不太可能的企业家之旅

原文:towardsdatascience.com/journey-of-an-unlikely-entrepreneur-d284e6653ef6?source=collection_archive---------16-----------------------#2024-10-01

沙丁鱼是一种诱饵鱼。(图片来源:NOAA 西南渔业科学中心

开发用于大物种钓鱼的海洋学应用

Sam McClatchieTowards Data Science Sam McClatchie

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

--

我要讲一个科学故事,纯粹是为了娱乐,因为我已经退休了,有时间,而且这个故事很有趣。这不是一个关于我如何构建我们的钓鱼地图产品的技术故事。这是讲述我从一名对工业毫无兴趣的渔业科学家,到成为一名不太可能的企业家,向拥有各种不同技术能力和知识的渔民们销售高科技地图产品的经历。这不是一个关于狂野成功的故事,远非如此,但它也不是一个关于初创公司失败的故事。它是一个关于鱼的故事,这应该会让我的同事们感到有趣,因为他们知道我其实并不喜欢鱼,尽管我是一个渔业海洋学家(见海洋学家的忏悔)。我更像是一个浮游生物专家。

当我在 1980 年代初期作为一名大胆的海洋学研究生刚开始时,我对为行业工作毫无兴趣。我认为自己会成为一名大学教授(但这一点没有实现)。每当生物学海洋学实验室在达尔豪西大学的电话响起,我们总开玩笑说是Scripps打来,想给我们其中之一提供工作机会。那个电话从未打来,但我们依然顺利毕业,且各自走上了不同的道路。我的道路带我走过了一系列的博士后和研究助理职位,然后进入了“永久”的政府科学家岗位,分别在新西兰、澳大利亚和加利福尼亚工作。最终,我确实在 NOAA 担任了 11 年CalCOFI 计划的负责人,在位于圣地亚哥 UCSD Scripps 校区的西南渔业科学中心(SWFSC)工作。

在那条道路上旅行时,我惊讶地看着,甚至有时带着钦佩,看到我的一些同事如何运用他们的技能成为了科学企业家。有些人成功了,有些则没有。一个人进入了遥感领域,然后转向仪器开发,迅速取得了很大的成功。另一个人则在努力维持一个深海摄像机的生意。还有一些人将他们的建模技能从政府的渔业评估转向了私人咨询。其他人则成立了一个声学调查公司。还有一人开发了一个追踪鱼类的系统,并因此成名。他们的努力让我着迷,但创业之路从未吸引我。我太忙于写研究论文了。那时我从未想到过有一天我会走上这条路。

CTD(导电性温度深度)仪器多重瓶部署以收集水样。(图片来源:加利福尼亚合作海洋渔业调查

快进到 37 年后(1985 到 2022 年)。在我的渔业海洋学家的职业生涯中,我学到了三件推动我进入创业领域的事:编程、遥感和渔民。作为学生时,便携式计算机上的电子表格让我大开眼界。我花了那么多时间盯着绿色的屏幕,以至于黑板上的粉笔变成了粉红色。后来,当我进入渔业声学领域时,电子表格失去了它的吸引力,因为数据超过了 10,000 行和数百列。我必须学习编写代码来处理我的分析。我已经写了数千行的 Turbo Pascal 代码来控制我在第一次博士后期间设计的实验系统,所以编程对我来说并不陌生。然而,这还是花了我一段时间。我在 40 多岁时学会了 R,50 多岁时学会了 NCL,60 多岁时学会了 Python。我像许多科学家一样通过将它们用于编码离散项目的分析,进而发表论文,来学习这些语言。最终,我学会了使用 Shell 脚本将这些脚本串联起来,创建结合 R 和 Python 的可重复工作流程。我开始使用 Linux,并与开源软件合作。我成了开放获取数据、开源软件和可重复开放科学的积极倡导者。

我一直对卫星遥感感到着迷,这项技术在我的职业生涯中真正爆发了。我从在论文中使用一些遥感图像,迅速转向向 NASA 请求数据。当我收到装满数据的 CD 盒时,我既感到困惑又激动,这些数据来自于海岸带色扫描仪,后来又来自其他传感器。处理这些数据简直是一个谜。每张 CD 上都有几十个非常大的文件,格式是 HDF 或其他难以理解的格式。该如何处理这些文件呢?我的软件无法打开这些文件。这些 CD 在我的办公室书架上放了好几年,成了我沮丧的象征。NASA 真的把 CD 寄给每个请求数据的人吗?我不确定,但我怀疑是的。我接着使用了 NASA 和 NOAA 提供的不同 GUI 界面。它们在查看少量图像时很有用,但在研究中显得笨拙。我尝试过使用 FTP,但进展不大,直到开发出了可以通过分析代码直接从远程访问并子集化数据的系统,避免了大规模下载的冗余。使用 R 和 Python,访问遥感数据变得简单、高效,并且可以编写脚本。

在我从浮游生物研究转向应用渔业研究之后,我在工作中接触到了渔民和渔业公司代表。我参加了无数次的规划会议、渔业评估会议和项目审查会议,会上有商业渔民和行业高层管理人员。我也参加了向渔民、非政府组织代表及其他利益相关者展示科学成果的会议。我意识到,商业渔民对科学评估过程有一定的尊重,但他们对科学结果也深感怀疑。他们的观点根植于经验,在面对通常不完整的科学证据时,这些观点很难改变。他们往往不信任这个过程,并且在政府渔业管理人员掌握主导时感到愤怒。他们的生计处于危险之中,这是一个强大的动机。他们有时认为政府科学家并不客观,而且在关于渔业资源状况的证据解释上常常存在长期的分歧。我发现这些会议让人感到压力很大,但它确实让我对渔民的思维方式有了些许了解。这对我后来开始向休闲大鱼钓鱼者销售遥感地图时非常有帮助。

我于 2018 年从 NOAA 渔业和 CalCOFI 计划退休,并返回新西兰。无需参加会议的感觉非常解脱,但我还是无法放弃科学。我开始学习 Python,并开始琢磨一个想法——为休闲大鱼钓鱼者制作卫星遥感地图。

在我开发出一个稳健的工作产品之前,经历了几次失败的尝试和死胡同。最初的想法是利用我的海洋学知识,制作海表温度(SST)和其他海洋变量(如叶绿素和洋流)的地图,供运动渔民用来引导他们到最有可能钓到马林鱼或金枪鱼的地点。这个概念在过程中发生了巨大变化。最初,我计划以非常低的费用出售单张地图,想法是将其作为一个低价值、高销量的产品。我们打算在线销售这些地图,价格低于一杯咖啡的费用。这个概念最终失败了,并且从技术角度来看也很难实现。我还打算提供全球任何地区的地图,但很快意识到这也是一个问题,不仅仅是在计算所需的时间方面,还因为我希望对我们提供地图的地区的海洋学有一定的了解。我们开始在新西兰、加利福尼亚和下加利福尼亚的游戏钓鱼港口地区提供地图包,这些地区我因为作为海洋学家的工作而非常熟悉,从而取得了一些进展。

接下来的挑战是如何成为一个知名的捕鱼地图提供商。市场上已经有一些成熟的公司,因此挑战不仅仅是让自己变得可见,还要获得市场份额。我开始在新西兰的一个休闲渔民 Facebook 小组页面上发布内容,吸引了几位具有科学背景的渔民的注意。他们主动联系我,并提供了宝贵的建议,告诉我他们希望从渔民的角度看到地图中哪些内容。接着,我们被管理新西兰大型游戏捕鱼 Facebook 小组的公司联系了。我们与他们签署了利润分享协议,但在 6 个月后解除了合作关系,因为尽管有最好的意图,合作关系并未对我们有利。在第一年,我们几乎所有早期的订阅者都来自新西兰,成功地大幅发展了业务。进入加利福尼亚/巴哈市场的难度更大,因为那里有更多的成熟竞争对手。

新西兰北岛 Whangamata 附近四天的海表温度(SST)。(地图由作者提供 Fishing Maps)

显示新西兰 Whangamata 附近叶绿素的地理位置图。(地图由作者提供 Fishing Maps

大型游戏捕鱼非常有季节性,因此其中一个挑战是在夏季捕鱼季节结束后,当订阅量下降时,如何保持动力。另一个挑战是管理对订阅者是否会在第二年回归的焦虑。像这样的新创公司在早期阶段绝对不是一个盈利的业务。收入被重新投入到新的计算机设备、网站开发和营销成本中。我的“Fishing Maps”合作伙伴(我的妻子)是一名会计师和审计师,负责追踪财务,确保我们的支出相对于收入保持在合理范围内。进入这个已有竞争者的市场是一个大胆的举动,但我意识到,其他公司也都是由非常小的团队运营的,在某些情况下,仅由一两个人经营,就像我们一样。我们的产品的一个独特之处在于我们详细的博客和文章,用通俗的语言准确解释了海洋学在游戏捕鱼中的应用。我们着眼于填补一个在小众市场中感知到的空白,通过提供高质量的卫星地图,结合海洋学知识,面向一个区域性的潜在市场。我们的目标不是去做其他供应商已经做得很好的事。我们是一个基于科学的产品。Fishing Maps的特色在于我们提供并正在开发的新指数,以及我们对渔民可以利用的鱼类发现结构的海洋学解释。

地图的开发过程仍在继续发展。来自渔民的反馈可能是具有挑战性的,但它能带来改进。从外部看待产品,可能会像收到科学论文的负面评论一样令人震惊和沮丧。另一方面,这也是一个集中改进的机会,且非常有效。然而,在像我们这样的初创公司中,平衡开发投入的时间与财务回报几乎是不可能的。

我们现在是新一波数字创业者的一部分,在家工作,几乎没有额外开销,拥有快速的互联网连接和创造高科技产品的知识。科学是有趣的,且始终在不断发展。而营销则不那么吸引人。为非专业人士写博客和文章是从撰写科学文章的巨大转变,但它也可以是值得的。从我在新西兰的书房望出去,可以看到美丽的怀塔基里山脉和曼努考港的海湾,这让我想起,只要你拥有开放的数据、开源软件、编程技能、时间以及尝试大胆事物的决心,一切皆有可能。

“评判 LLM 法官”:LLM 应用评估持续改进的双层评估(QA)框架

原文:towardsdatascience.com/judge-an-llm-judge-a-dual-layer-evaluation-framework-for-continous-improvement-of-llm-apps-7450d0e81e17?source=collection_archive---------5-----------------------#2024-07-17

“由 LLM 法官对 LLM 应用进行评估”是否可以通过另一个 LLM 法官进行审计,以实现评估过程的持续改进?

Daniel Khoa LeTowards Data Science Daniel Khoa Le

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

--

LLM 应用评估的持续改进框架——无参考方法 —— 图像由作者提供

TLDR

本文解释了使用 LLM 法官评估另一个 LLM 法官的概念和低抽象实现。其目的是改进 LLM 应用的评估过程,减少 LLM 法官未能做出公正评估的情况。

目录

  • 引言

  • 研究问题

  • 实验设计

  • 实现

  • 实验结果

  • 结论

👉 引言

❇️ 在构建 LLM 应用的领域,如何确保一致且可靠的性能是关于质量保证(QA)中最常见的提问之一。由于 LLM 模型的非确定性特性,它们的输出可能具有极大的变化性。因此,LLM 应用的严格评估是绝对必要的。没有良好的评估方法,我们必须接受一定程度的风险(例如客户投诉等),因为无法及时识别 LLM 应用中的意外行为。 常见的 LLM 评估方法包括启发式评估、LLM 作为法官和人工评审。

  • 📍 启发式评估者:例如,检查输出是否等于“yes”或输出是否大于 10 的函数。

  • 📍 LLM 作为法官:使用 LLM 对另一个 LLM 的输出进行评判。

  • 📍人工评判者:使用人工评估 LLM 的输出。

❇️ 使用 LLM 评判者是一个最佳选择,因为它可以自动化,并且比人工评判者便宜(且更可行)。此外,LLM 评判者可以处理自由文本格式,这与启发式评估者不同。然而,LLM 的非确定性特征意味着即使在控制了参数的情况下,输出也可能有所不同,这引发了对这些判断可靠性的担忧。

💥 今天我们将讨论的问题:

当选择 LLM 评判者来评估我们的 LLM 应用时,我们还应质疑 LLM 评判者本身的完整性。

✅ 因此,下面描述的实验旨在确定我们是否可以使用一个 LLM 评判者(我们称之为“最高 LLM 评判者”)来评估另一个 LLM 评判者的判断,而无需任何真实参考(无参考评估)。最终目标是找到改善第一个 LLM 评判者的方法。

下图解释了这样一个框架。

使用 LLM 评判者评判另一个 LLM 评判者(无参考)的高层架构——图片来自作者

👉 研究问题

“LLM 评判者对 LLM 应用的评估”是否可以由另一个 LLM 评判者进行审计,以便不断改进评估过程?

👉 实验设计

🔹 在此实验中必须提到的一个重要限制条件是,两个 LLM 评判者都将进行无真实参考的评估。如果使用真实参考进行评估,评判者将得到正确答案并要求进行比较。然而,对于大多数没有人工策划的数据集的场景,采用无参考评估是首选方法。

🔹 提出的框架通过增加一个最高 LLM 评判者,改进了传统的单层 LLM 应用评估。我们可以为这个框架提供两种方法。

  • 方法 1:一个 LLM 应用程序由一个 LLM 评判者进行评估,其判断随后由最高 LLM 评判者(无参考)进行审查。不同意见或异常情况随后由人工进行审查。

方法 1 — 图片来自作者

  • 方法 2:LLM 评判者和最高 LLM 评判者独立评估 LLM 应用程序(无参考)。然后比较评判结果,任何差异都会标记出来进行人工审查。

方法 1 将在本文中进一步讨论。

👉 实现

🔹 我选择的上述框架实现关注的是高层次概念,而没有深入探讨完美性能的微调。

未使用任何 LLM 评估库或平台(如 LangChain、LangSmith、LangFuse 等)。代码实现的抽象程度较低,便于读者跟随,而不会迷失在复杂的代码细节中。

由于引用LLM 评审员最高级 LLM 评审员可能比较难以理解,我们为评估设置中的组件分配了名义角色:

  • LLM Application ➡️学生

  • LLM Judge ➡️ 教师

  • Supreme LLM Judge ➡️ 评审员

💥 完整代码可以在 这个仓库找到。

# LLM Application
def trigger_llm_app(context: str, question: str):
    fmt_context_and_question = f"""Context: {context}\nQuestion: {question}"""
    messages = [
        llm_app_prompt,
        {"role": "user",
         "content": fmt_context_and_question}
    ]

    response = openai_client.chat.completions.create(messages=messages,
                                                     model="gpt-3.5-turbo")
    return response.choices[0].message.content

# LLM Judge
def eval_llm_app(context: str, question: str, predicted_answer: str):
    fmt_input = f"""Context: {context}\nQuestion: {
        question}\nStudent's Answer: {predicted_answer}"""
    messages = [
        llm_judge_prompt,
        {"role": "user",
         "content": fmt_input}
    ]

    response = openai_client.chat.completions.create(messages=messages,
                                                     model="gpt-3.5-turbo")
    return response.choices[0].message.content

# Superior LLM Judge
def eval_llm_judge(context: str, question: str, student_answer: str, teacher_grading: str):
    fmt_input = f"""Context: {context}\nQuestion: {question}\nStudent's Answer: {
        student_answer}\nTeacher's Grading: {teacher_grading}"""
    messages = [
        supreme_llm_judge_prompt,
        {"role": "user",
         "content": fmt_input}
    ]

    response = openai_client.chat.completions.create(messages=messages,
                                                     model="gpt-4")
    return response.choices[0].message.content

本实验设计中的一个微妙但重要的决策是使用 GPT-4 作为最高级 LLM 评审员,而 LLM 应用程序和 LLM 评审员使用的是 GPT-3.5-turbo。这样可以确保最高级 LLM 评审员的评估更加稳健和可靠(关于比较的更多信息请阅读这里)。

本实验中各个组件的提示如下。你可以看到,我使用了少样本提示技术来提高评估输出的一致性。

 llm_app_prompt = {"role": "system",
                  "content": """You are a helpful assistant. Please use step-by-step reasoning to address questions based on the specific context provided."""}

llm_judge_prompt = {
    "role": "system",
    "content": """You are a math teacher tasked with grading a student's answer.
        Evaluate the student's response considering the context of the question, the correctness of the answer, and the reasoning provided.
        Conclude with a grade: assign '0' if the answer and the reasoning is incorrect and '1' if it is correct.
        Your grading output should be strictly in this format (no other words allowed): 'Grade: 0' or 'Grade: 1'.

        Below are examples for your reference:
            - Example:
            Question: How long does it take to drive 100 kilometers at 50 kilometers per hour?
            Student's Answer: To find the time, divide the distance by the speed: 100 km / 50 km/h = 2 hours.
            Grade: 1

            - Example:
            Question: Calculate the area of a square with a side length of 5 meters.
            Student's Answer: Given that the side length of the square is 5 meters, the answer is: 5*4=20 square meters.
            Grade: 0

            - Example:
            Question: How many seconds are in an hour?
            Student's Answer: 3600 seconds
            Grade: 1

            - Example:
            Question: Given two sets, Set A containing the elements 1, 2, and 3, and Set B containing the elements 3, 4, and 5, what is the intersection of Set A and Set B?
            Student's Answer: The element that is common to both sets is 1.
            Grade: 0
        """
}

supreme_llm_judge_prompt = {
    "role": "system",
    "content": """You are an exam reviewer tasked with evaluating teachers' grading. Your job is to review the grade given by the teacher to a student's answer and assess its correctness.
                Important: Your review is of the teacher's grading, not the student's answer.
                Output Format: Your review output should be strictly in this format (no other words allowed): 'Correctness: 0' or 'Correctness: 1'.

                Below are examples for your reference:
                    - Example:
                    Question: How long does it take to drive 100 kilometers at 50 kilometers per hour?
                    Student's Answer: To find the time, divide the distance by the speed: 100 km / 50 km/h = 2 hours.
                    Grade: 1
                    Correctness: 1

                    - Example:
                    Question: Calculate the area of a square with a side length of 5 meters.
                    Student's Answer: Given that the side length of the square is 5 meters, the answer is: 5*4=20 square meters.
                    Grade: 0
                    Correctness: 1

                    - Example:
                    Question: How many seconds are in an hour?
                    Student's Answer: 3600 seconds
                    Grade: 0
                    Correctness: 0

                    - Example:
                    Question: Given two sets, Set A containing the elements 1, 2, and 3, and Set B containing the elements 3, 4, and 5, what is the intersection of Set A and Set B?
                    Student's Answer: The element that is common to both sets is 1.
                    Grade: 1
                    Correctness: 0
         """
}

🔹 我们问 LLM 应用程序的问题是:

在一个能说英语或德语的 30 人小组中,有 10 人能说两种语言,25 人能说德语。

多少人只会说英语?

LLM 应用程序不仅需要提供正确答案,还需要解释其推理过程。LLM 评审员随后评估此输出——包括最终答案和推理过程。最后,最高级 LLM 评审员将评估 LLM 评审员给出的评估。

你可以注意到,我在这个问题的背景中留了一些冗余信息,以挑战 LLM 应用程序。

🔹 我使用 OpenAI API 默认温度设置,运行了 100 次评估周期,使用相同的问题来检验评审的表现。

if __name__ == "__main__":
    context = "In a group of 30 people who can speak either English or German, 10 can speak both, and 25 can speak German."
    user_question = "How many speak only English?"

    list_results = []
    for i in range(100):
        print(f"===> Iteration {i+1}")
        list_results.append(evaluate(context, user_question))

👉 实验结果

💥 再次提醒,在阅读我们的结果之前,请记住我们的定义:

  • LLM Application ➡️学生

  • LLM Judge ➡️ 教师

  • Supreme LLM Judge ➡️ 评审员

💥 重要: 我们定义“正例”为教师评估错误的情况。

我们将通过以下指标来衡量评审员(最高级 LLM 评审员)的表现。

  • recall_of_reviewer: 衡量评审员识别所有正例的能力。它表示评审员能多有效地从教师的评估中捕捉到错误。

  • precision_of_reviewer: 定义为评审员识别出的正例中,实际为正例的比例。

精确度和召回率之间总是存在权衡。你希望在预测中捕捉到更多的真正正例(且不太关注假正例),那么你的模型就会变得不那么精确。

reviewer_precision: 0.43
reviewer_recall: 0.70

让我们重新审视我们的研究问题,看看最高级 LLM 评审员(评审员)如何帮助审查 LLM 评审员(教师)的工作。

最高级 LLM 评审员能够识别出 LLM 评审员做出错误评估的 70%的案例。通过分析这些已识别的案例,我们可以了解 LLM 评审员为何会产生困惑,从而改进我们 LLM 应用的评估流程。

😮 你可能会好奇,最高级 LLM 评审员捕捉到的 LLM 评审员错误判定。

以下是评审员成功识别教师评分错误的示例。通过这些示例,我们可以研究 LLM 评审员表现不佳的原因。

👋 在阅读以下示例之前,关于“人工评审员”的说明:

  • 是的,我(作者)是人工评审员!

  • 在本实验的背景下,如果学生的推理是合理的,即使回答冗长且包含多余的计算和推理,人工评审员也会判定其答案为正确。

  • 请注意,这个human_grading是针对学生的答案进行的。

 {
        "student_answer": "1\. We know that there are 10 people who can speak both English and German.\n2\. There are 25 people who can speak German.\n3\. To find out how many speak only English, we need to subtract the number of people who can speak both English and German from the total number of people who can speak German.\n4\. So, the number of people who speak only English is 25 (people who speak German) - 10 (people who speak both) = 15 people.\n5\. Therefore, there are 15 people who speak only English in the group of 30 people.",
        "teacher_grading": "1",
        "reviewer_feedback": "0",
        "human_grading": "0"
    },
    {
        "student_answer": "To find out how many people speak only English, we first calculate the total number of people who can speak English by subtracting the number of people who can speak both English and German (10) from the total number of people who can speak German (25). This is because those 10 people are counted twice - once in the English-speaking group and once in the German-speaking group.\n\nTotal number of people who can speak English = Total number of people who can speak German - Number of people who can speak both English and German\nTotal number of people who can speak English = 25 - 10\nTotal number of people who can speak English = 15\n\nTherefore, there are 15 people who speak only English.",
        "teacher_grading": "1",
        "reviewer_feedback": "0",
        "human_grading": "0"
    },
    {
        "student_answer": "Given that 10 people can speak both English and German, and 25 people can speak German, it means that 25 - 10 = 15 people speak only German.\nSince there are 30 people in total and we have now found the number of people who speak only German, the number of people who speak only English is 30 - 15 - 10 = 5.\nTherefore, 5 people speak only English.",
        "teacher_grading": "0",
        "reviewer_feedback": "0",
        "human_grading": "1"
    },
    {
        "student_answer": "To find out how many people speak only English, we can start by calculating the total number of people who can speak English. \n\nFrom the information provided, we know that there are 10 people who can speak both English and German. Since there are a total of 30 people in the group, and 25 of them can speak German, the remaining 5 people must be the ones who speak only English.\n\nTherefore, the number of people who speak only English is 5.",
        "teacher_grading": "0",
        "reviewer_feedback": "0",
        "human_grading": "1"
    },
    {
        "student_answer": "To find out how many people speak only English, we will first subtract the total number of German speakers from the total number of people in the group:\n\nTotal number of people = 30\nNumber of German speakers = 25\n\nTherefore, the number of people who speak only English is:\n30 - 25 = 5\n\nSo, there are 5 people in the group who speak only English.",
        "teacher_grading": "0",
        "reviewer_feedback": "0",
        "human_grading": "1"
    },

👉 结论

✅ 由最高级 LLM 评审员对 LLM 评审员的评估为我们提供了关于多层次评估系统在 LLM 应用中的有效性的见解。最高级 LLM 评审员成功识别了70%的召回率, 成功捕捉到 LLM 评审员做出的 70%的错误评估。对于无参考评估的情况以及概念验证实现来说,这个结果并不差。这些捕捉到的错误评估可能会帮助我们找到解决方案,持续改进我们的 LLM 评审员。

LLM 评审员的持续改进 — 图片由作者提供

我们在使用 LLM 评审员来评估 LLM 应用时遇到的困难,同样适用于使用最高级 LLM 评审员来评判 LLM 评审员(绕口令!)。除了追求高准确度的评估外,确保评估结果的一致性也是一大挑战。

✅ 由于第二层评估需要耗费相当多的人工评估工作,这种方法更适合用于审计或定期的离线评估,而非持续性的评估。

同样值得注意的是,使用随机抽样方法进行评估可能是节省资源的好方法。 通过战略性地部署第二层的大型语言模型评估,配合人工评审,我们可以增强评估系统的整体可靠性和准确性,从而有助于高效的大型语言模型应用。

关于我

我是 Daniel Le,现居柏林。目前我从事机器学习和数据工程领域的工作。

我对新技术感兴趣,并且关注它们如何应用于解决现实世界中的问题。

如果您有任何问题或希望进一步讨论这些兴趣,欢迎通过LinkedIn与我联系。

参考文献

使用高级检索 LlamaPacks 启动您的 RAG 流水线,并通过 Lighthouz AI 进行基准测试

原文:towardsdatascience.com/jump-start-your-rag-pipelines-with-advanced-retrieval-llamapacks-and-benchmark-with-lighthouz-ai-80a09b7c7d9d?source=collection_archive---------4-----------------------#2024-01-29

探索使用 LlamaPacks、Lighthouz AI 和 Llama Guard 开发强大的 RAG

Wenqi GlantzTowards Data Science Wenqi Glantz

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

--

由作者使用 DALL-E 3 生成的图片

自 2023 年 11 月下旬发布以来,LlamaPacks 已策划了超过 50 个包,帮助启动您的 RAG 流水线开发。在这些包中,许多高级检索包应运而生。在本文中,我们将深入探讨七个高级检索包;请参见下方的图表。

图片来源:LlamaIndex 关于七个高级检索 LlamaPacks 的推文

我们将执行两个步骤:

  • 给定一个使用场景,我们将使用 Lighthouz AutoBench 生成基准测试,并通过 Lighthouz Eval Studio 评估这些包,以确定从 LLM 安全角度哪个最适合我们的使用场景。

  • 一旦确定了最优包,我们将把 Llama Guard 加入 RAG 流水线,调整其自定义分类法,重新通过 Eval Studio 评估,并观察诸如提示注入等类别的评估分数如何变化。

用于高级检索的 LlamaPacks

首先,让我们看看这七个高级检索 LlamaPacks,了解它们如何在…

K 最近邻分类器解析:初学者的可视化指南与代码示例

原文:towardsdatascience.com/k-nearest-neighbor-classifier-explained-a-visual-guide-with-code-examples-for-beginners-a3d85cad00e1?source=collection_archive---------2-----------------------#2024-08-20

分类算法

机器学习中的友好邻居方法

Samy BaladramTowards Data Science Samy Baladram

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

--

⛳️ 更多分类算法解析:· 虚拟分类器 ▶ K 最近邻分类器 · 伯努利朴素贝叶斯 · 高斯朴素贝叶斯 · 决策树分类器 · 逻辑回归 · 支持向量分类器 · 多层感知机

想象一种方法,通过查看之前见过的最相似的例子来做出预测。这就是最近邻分类器的本质——一个简单而直观的算法,它为机器学习带来了一丝现实世界的逻辑。

虽然 虚拟分类器设定了最低的性能标准,但最近邻方法模仿了我们在日常生活中做决策的方式:通过回忆类似的过去经历。这就像是问你的邻居今天根据天气如何穿衣,从而决定你应该穿什么。在数据科学领域,这个分类器通过检查最接近的数据点来做出预测。

所有视觉元素:作者使用 Canva Pro 创建。已优化为适合移动设备;在桌面上可能会显得过大。

定义

K 最近邻分类器是一个机器学习模型,它根据特征空间中 K 个最近数据点的多数类别来做出预测。KNN 算法假设相似的事物会存在于近距离内,这使得它直观且易于理解。

最近邻方法是机器学习中最简单的算法之一。

📊 使用的数据集

在本文中,我们将使用这个简单的人工高尔夫数据集(灵感来自 [1])作为例子。这个数据集预测一个人是否会根据天气条件打高尔夫。它包括天气、温度、湿度和风等特征,目标变量是是否打高尔夫。

列:‘Outlook’(天气)、‘Temperature’(温度)、‘Humidity’(湿度)、‘Wind’(风)和‘Play’(目标特征)

# Import libraries
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
import pandas as pd
import numpy as np

# Make the dataset
dataset_dict = {
    'Outlook': ['sunny', 'sunny', 'overcast', 'rainy', 'rainy', 'rainy', 'overcast', 'sunny', 'sunny', 'rainy', 'sunny', 'overcast', 'overcast', 'rainy', 'sunny', 'overcast', 'rainy', 'sunny', 'sunny', 'rainy', 'overcast', 'rainy', 'sunny', 'overcast', 'sunny', 'overcast', 'rainy', 'overcast'],
    'Temperature': [85.0, 80.0, 83.0, 70.0, 68.0, 65.0, 64.0, 72.0, 69.0, 75.0, 75.0, 72.0, 81.0, 71.0, 81.0, 74.0, 76.0, 78.0, 82.0, 67.0, 85.0, 73.0, 88.0, 77.0, 79.0, 80.0, 66.0, 84.0],
    'Humidity': [85.0, 90.0, 78.0, 96.0, 80.0, 70.0, 65.0, 95.0, 70.0, 80.0, 70.0, 90.0, 75.0, 80.0, 88.0, 92.0, 85.0, 75.0, 92.0, 90.0, 85.0, 88.0, 65.0, 70.0, 60.0, 95.0, 70.0, 78.0],
    'Wind': [False, True, False, False, False, True, True, False, False, False, True, True, False, True, True, False, False, True, False, True, True, False, True, False, False, True, False, False],
    'Play': ['No', 'No', 'Yes', 'Yes', 'Yes', 'No', 'Yes', 'No', 'Yes', 'Yes', 'Yes', 'Yes', 'Yes', 'No', 'No', 'Yes', 'Yes', 'No', 'No', 'No', 'Yes', 'Yes', 'Yes', 'Yes', 'Yes', 'Yes', 'No', 'Yes']
}
original_df = pd.DataFrame(dataset_dict)

print(original_df)

KNN 算法要求首先对数据进行缩放。将类别列转换为 0 和 1,并且 缩放数值特征,以确保没有单个特征主导距离度量。

类别列(天气和风)使用独热编码进行编码,而数值列则使用标准化缩放(z 标准化)。此过程分别在训练集和测试集上进行。

from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

# Preprocess data
df = pd.get_dummies(original_df, columns=['Outlook'], prefix='', prefix_sep='', dtype=int)
df['Wind'] = df['Wind'].astype(int)
df['Play'] = (df['Play'] == 'Yes').astype(int)
df = df[['sunny','rainy','overcast','Temperature','Humidity','Wind','Play']]

# Split data and standardize features
X, y = df.drop(columns='Play'), df['Play']
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.5, shuffle=False)

scaler = StandardScaler()
float_cols = X_train.select_dtypes(include=['float64']).columns
X_train[float_cols] = scaler.fit_transform(X_train[float_cols])
X_test[float_cols] = scaler.transform(X_test[float_cols])

# Print results
print(pd.concat([X_train, y_train], axis=1).round(2), '\n')
print(pd.concat([X_test, y_test], axis=1).round(2), '\n')

主要机制

KNN 分类器通过找到离新数据点最近的 K 个邻居,然后对这些邻居中最常见的类别进行投票来进行工作。以下是其工作原理:

  1. 计算新数据点与训练集中的所有点之间的距离。

  2. 根据这些距离选择 K 个最近的邻居。

  3. 对这 K 个邻居的类别进行多数投票。

  4. 将多数类别分配给新数据点。

对于我们的高尔夫数据集,KNN 分类器可能会查看过去 5 个最相似的天气条件,以预测某人今天是否会打高尔夫。

训练步骤

与许多其他算法不同,KNN 没有明确的训练阶段。相反,它记住整个训练数据集。以下是其过程:

  1. 选择一个 K 值(要考虑的邻居数量)。

在 2D 设置中,就像是找出最接近的颜色的多数。

from sklearn.neighbors import KNeighborsClassifier

# Select the Number of Neighbors ('k')
k = 5

2. 选择一个距离度量(例如,欧几里得距离、曼哈顿距离)。

最常见的距离度量是欧几里得距离。这就像是找出两个点之间的直线距离。

import numpy as np

# Choose a Distance Metric
distance_metric = 'euclidean'

# Trying to calculate distance between ID 0 and ID 1
print(np.linalg.norm(X_train.loc[0].values - X_train.loc[1].values))

3. 存储/记住所有训练数据点及其对应的标签。

# Initialize the k-NN Classifier
knn_clf = KNeighborsClassifier(n_neighbors=k, metric=distance_metric)

# "Train" the kNN (although no real training happens)
knn_clf.fit(X_train, y_train)

分类步骤

一旦最近邻分类器被“训练”(即训练数据已存储),它将如何为新实例做出预测:

  1. 距离计算:对于新的实例,使用选择的距离度量计算其与所有存储的训练实例之间的距离。

对于 ID 14,我们计算其与训练集每个成员(ID 0 — ID 13)之间的距离。

from scipy.spatial import distance

# Compute the distances from the first row of X_test to all rows in X_train
distances = distance.cdist(X_test.iloc[0:1], X_train, metric='euclidean')

# Create a DataFrame to display the distances
distance_df = pd.DataFrame({
    'Train_ID': X_train.index,
    'Distance': distances[0].round(2),
    'Label': y_train
}).set_index('Train_ID')

print(distance_df.sort_values(by='Distance'))

2. 邻居选择与预测:基于计算的距离,识别 K 个最近的邻居,然后将这些邻居中最常见的类作为新实例的预测类。

在计算了它与所有存储数据点的距离,并按从低到高排序后,我们识别出 5 个最近的邻居(前 5)。如果这些邻居中大多数(3 个或更多)标记为“NO”,我们为 ID 14 预测“NO”。

# Use the k-NN Classifier to make predictions
y_pred = knn_clf.predict(X_test)
print("Label     :",list(y_test))
print("Prediction:",list(y_pred))

评估步骤

通过这个简单的模型,我们能获得足够好的准确性,远远优于随机猜测!

from sklearn.metrics import accuracy_score

# Evaluation Phase
accuracy = accuracy_score(y_test, y_pred)
print(f'Accuracy: {accuracy.round(4)*100}%')

关键参数

虽然 KNN 在概念上简单,但它确实有一些重要的参数:

  1. K:要考虑的邻居数量。较小的 K 值可能导致对噪声敏感的结果,而较大的 K 值可能会平滑决策边界。

k 值越大,越有可能选择大多数类(“YES”)。

labels, predictions, accuracies = list(y_test), [], []

k_list = [3, 5, 7]
for k in k_list:
    knn_clf = KNeighborsClassifier(n_neighbors=k)
    knn_clf.fit(X_train, y_train)
    y_pred = knn_clf.predict(X_test)
    predictions.append(list(y_pred))
    accuracies.append(accuracy_score(y_test, y_pred).round(4)*100)

df_predictions = pd.DataFrame({'Label': labels})
for k, pred in zip(k_list, predictions):
    df_predictions[f'k = {k}'] = pred

df_accuracies = pd.DataFrame({'Accuracy ': accuracies}, index=[f'k = {k}' for k in k_list]).T

print(df_predictions)
print(df_accuracies)

2. 距离度量:这决定了点与点之间相似度的计算方式。常见选项包括:

  • 欧几里得距离(直线距离)

  • 曼哈顿距离(绝对差值之和)

  • 闵可夫斯基距离(欧几里得距离和曼哈顿距离的泛化)

3. 权重函数:这决定了如何对每个邻居的贡献进行加权。选项包括:

  • ‘uniform’:所有邻居的权重相等。

  • ‘distance’:较近的邻居比较远的邻居对结果有更大的影响。

优缺点

就像机器学习中的任何算法一样,KNN 也有其优点和局限性。

优点:

  1. 简洁性:容易理解和实现。

  2. 无假设:不对数据分布做任何假设。

  3. 多功能性:可以用于分类和回归任务。

  4. 无需训练阶段:可以快速整合新数据,无需重新训练。

缺点:

  1. 计算开销大:每次预测时需要计算所有训练样本的距离。

  2. 内存密集型:需要存储所有训练数据。

  3. 对无关特征敏感:可能会受到与分类无关的特征的干扰。

  4. 维度灾难:在高维空间中,性能会下降。

最后备注

K 最近邻(KNN)分类器作为机器学习中的基础算法,因其直观且高效的分类方法而脱颖而出。其简单性使其成为初学者的理想起点,而其多功能性确保了对经验丰富的数据科学家的价值。KNN 的强大之处在于,它能够根据数据点的接近度进行预测,无需复杂的训练过程。

然而,必须记住,KNN 只是庞大机器学习工具箱中的一个工具。在数据科学旅程中前进时,可以将 KNN 作为理解更复杂算法的垫脚石,在选择模型时始终考虑你的数据特性和问题需求。通过掌握 KNN,你将获得对分类技术的宝贵见解,为应对更复杂的机器学习挑战奠定坚实的基础。

🌟 k 最近邻分类器代码总结

# Import libraries
import pandas as pd
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score

# Load data
dataset_dict = {
    'Outlook': ['sunny', 'sunny', 'overcast', 'rainy', 'rainy', 'rainy', 'overcast', 'sunny', 'sunny', 'rainy', 'sunny', 'overcast', 'overcast', 'rainy', 'sunny', 'overcast', 'rainy', 'sunny', 'sunny', 'rainy', 'overcast', 'rainy', 'sunny', 'overcast', 'sunny', 'overcast', 'rainy', 'overcast'],
    'Temperature': [85.0, 80.0, 83.0, 70.0, 68.0, 65.0, 64.0, 72.0, 69.0, 75.0, 75.0, 72.0, 81.0, 71.0, 81.0, 74.0, 76.0, 78.0, 82.0, 67.0, 85.0, 73.0, 88.0, 77.0, 79.0, 80.0, 66.0, 84.0],
    'Humidity': [85.0, 90.0, 78.0, 96.0, 80.0, 70.0, 65.0, 95.0, 70.0, 80.0, 70.0, 90.0, 75.0, 80.0, 88.0, 92.0, 85.0, 75.0, 92.0, 90.0, 85.0, 88.0, 65.0, 70.0, 60.0, 95.0, 70.0, 78.0],
    'Wind': [False, True, False, False, False, True, True, False, False, False, True, True, False, True, True, False, False, True, False, True, True, False, True, False, False, True, False, False],
    'Play': ['No', 'No', 'Yes', 'Yes', 'Yes', 'No', 'Yes', 'No', 'Yes', 'Yes', 'Yes', 'Yes', 'Yes', 'No', 'No', 'Yes', 'Yes', 'No', 'No', 'No', 'Yes', 'Yes', 'Yes', 'Yes', 'Yes', 'Yes', 'No', 'Yes']
}
df = pd.DataFrame(dataset_dict)

# Preprocess data
df = pd.get_dummies(df, columns=['Outlook'], prefix='', prefix_sep='', dtype=int)
df['Wind'] = df['Wind'].astype(int)
df['Play'] = (df['Play'] == 'Yes').astype(int)

# Split data
X, y = df.drop(columns='Play'), df['Play']
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.5, shuffle=False)

# Standardize features
scaler = StandardScaler()
float_cols = X_train.select_dtypes(include=['float64']).columns
X_train[float_cols] = scaler.fit_transform(X_train[float_cols])
X_test[float_cols] = scaler.transform(X_test[float_cols])

# Train model
knn_clf = KNeighborsClassifier(n_neighbors=3, metric='euclidean')
knn_clf.fit(X_train, y_train)

# Predict and evaluate
y_pred = knn_clf.predict(X_test)
print(f"Accuracy: {accuracy_score(y_test, y_pred)}")

进一步阅读

若想详细了解 KNeighborsClassifier 及其在 scikit-learn 中的实现,读者可以参考官方文档 [2],该文档提供了关于其用法和参数的详细信息。

技术环境

本文使用 Python 3.7 和 scikit-learn 1.5。虽然所讨论的概念通常适用,但不同版本之间的具体代码实现可能会有所不同。

插图说明

除非另有说明,所有图片均由作者创作,融合了 Canva Pro 的授权设计元素。

如需简明的 K 最近邻视觉总结,请查看 配套 Instagram 帖子。

参考文献

[1] T. M. Mitchell, 机器学习(1997),麦格劳-希尔科学/工程/数学, 第 59 页

𝙎𝙚𝙚 𝙢𝙤𝙧𝙚 𝘾𝙡𝙖𝙨𝙨𝙞𝙛𝙞𝙘𝙖𝙩𝙞𝙤𝙣 𝘼𝙡𝙜𝙤𝙧𝙞𝙩𝙝𝙢𝙨 𝙝𝙚𝙧𝙚:

Samy Baladram

Samy Baladram

分类算法

查看列表8 个故事

𝙔𝙤𝙪 𝙢𝙞𝙜𝙝𝙩 𝙖𝙡𝙨𝙤 𝙡𝙞𝙠𝙚:

Samy Baladram

Samy Baladram

回归算法

查看列表5 个故事一个带着辫子和粉色帽子的卡通玩偶。这个“假人”玩偶,通过其简单的设计和心形装饰的衣服,形象地代表了机器学习中假回归器的概念。就像这个玩具般的形象是对人的简化静态表现,假回归器则是作为基准模型,供更复杂的分析使用。Samy Baladram

Samy Baladram

集成学习

查看列表4 个故事

K 最近邻回归器,解释:带代码示例的视觉指南

原文:towardsdatascience.com/k-nearest-neighbor-regressor-explained-a-visual-guide-with-code-examples-df5052c8c889?source=collection_archive---------1-----------------------#2024-10-07

回归算法

使用 KD 树和 Ball 树快速寻找邻居

Samy BaladramTowards Data Science Samy Baladram

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

--

## K 近邻分类器,解释:带代码示例的初学者视觉指南

机器学习中的友好邻居方法

[towardsdatascience.com

在我们探讨了最近邻分类器后,让我们转向回归领域中的“兄弟”——最近邻回归器。最近邻回归器将相同的直观概念应用于预测连续值。但是,随着数据集的增大,如何高效地找到这些邻居变得非常麻烦。这就是 KD 树和 Ball 树派上用场的地方。

令人沮丧的是,目前没有明确的指南能够真正解释这些算法的运作原理。当然,有一些二维可视化图像,但它们通常无法清楚地展示树在多维环境中的工作方式。

在这里,我们将解释这些算法实际是如何运作的,而不使用过于简化的二维表示。我们将专注于树的构建过程,并看看哪些计算(和数字)实际上是重要的。

所有可视化:作者使用 Canva Pro 创建,针对移动设备进行了优化;在桌面上可能显示过大。

定义

最近邻回归器是一个直接的预测模型,通过平均附近数据点的结果来估算值。该方法基于相似输入可能产生相似输出的理念。

最近邻方法是机器学习工具包中最基础且强大的技术之一。它们的简单性掩盖了它们在许多实际场景中的有效性。

📊 使用的数据集

为了说明我们的概念,我们将使用我们通常的数据集。这个数据集有助于预测每天的高尔夫球手数量。它包括天气状况、温度、湿度和风速等因素。我们的目标是基于这些变量估算每日高尔夫球手的到场人数。

列:‘Outlook’(天气状况),‘Temperature’(温度,单位华氏度),‘Humidity’(湿度,单位%),‘Wind’(风速,是否有风),以及‘Number of Players’(玩家数量,数值型,目标特征)

为了有效地使用最近邻回归,我们需要对数据进行预处理。这包括将分类变量转换为数值格式和缩放数值特征。

对‘Temperature’(温度)和‘Humidity’(湿度)应用标准缩放,而对‘Outlook’(天气状况)和‘Wind’(风速)应用独热编码。

# Import libraries
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.metrics import root_mean_squared_error
from sklearn.neighbors import KNeighborsRegressor
from sklearn.preprocessing import StandardScaler
from sklearn.compose import ColumnTransformer

# Create dataset
dataset_dict = {
    'Outlook': ['sunny', 'sunny', 'overcast', 'rain', 'rain', 'rain', 'overcast', 'sunny', 'sunny', 'rain', 'sunny', 'overcast', 'overcast', 'rain', 'sunny', 'overcast', 'rain', 'sunny', 'sunny', 'rain', 'overcast', 'rain', 'sunny', 'overcast', 'sunny', 'overcast', 'rain', 'overcast'],
    'Temperature': [85.0, 80.0, 83.0, 70.0, 68.0, 65.0, 64.0, 72.0, 69.0, 75.0, 75.0, 72.0, 81.0, 71.0, 81.0, 74.0, 76.0, 78.0, 82.0, 67.0, 85.0, 73.0, 88.0, 77.0, 79.0, 80.0, 66.0, 84.0],
    'Humidity': [85.0, 90.0, 78.0, 96.0, 80.0, 70.0, 65.0, 95.0, 70.0, 80.0, 70.0, 90.0, 75.0, 80.0, 88.0, 92.0, 85.0, 75.0, 92.0, 90.0, 85.0, 88.0, 65.0, 70.0, 60.0, 95.0, 70.0, 78.0],
    'Wind': [False, True, False, False, False, True, True, False, False, False, True, True, False, True, True, False, False, True, False, True, True, False, True, False, False, True, False, False],
    'Num_Players': [52, 39, 43, 37, 28, 19, 43, 47, 56, 33, 49, 23, 42, 13, 33, 29, 25, 51, 41, 14, 34, 29, 49, 36, 57, 21, 23, 41]
}

df = pd.DataFrame(dataset_dict)

# One-hot encode 'Outlook' column
df = pd.get_dummies(df, columns=['Outlook'])

# Convert 'Wind' column to binary
df['Wind'] = df['Wind'].astype(int)

# Split data into features and target, then into training and test sets
X, y = df.drop(columns='Num_Players'), df['Num_Players']
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.5, shuffle=False)

# Identify numerical columns
numerical_columns = ['Temperature', 'Humidity']

# Create a ColumnTransformer to scale only numerical columns
ct = ColumnTransformer([
    ('scaler', StandardScaler(), numerical_columns)
], remainder='passthrough')

# Fit the ColumnTransformer on the training data and transform both training and test data
X_train_transformed = ct.fit_transform(X_train)
X_test_transformed = ct.transform(X_test)

# Convert the transformed data back to DataFrames
feature_names = numerical_columns + [col for col in X_train.columns if col not in numerical_columns]
X_train_scaled = pd.DataFrame(X_train_transformed, columns=feature_names, index=X_train.index)
X_test_scaled = pd.DataFrame(X_test_transformed, columns=feature_names, index=X_test.index)

主要机制

最近邻回归器的工作原理类似于其分类器对等物,但它不是对类别进行投票,而是对目标值进行平均。基本过程如下:

  1. 计算新数据点与训练集中所有点之间的距离。

  2. 根据这些距离选择 K 个最近邻居。

  3. 计算这些 K 个邻居的目标值的平均值。

  4. 将这个平均值作为新数据点的预测值。

上述方法,即使用所有数据点来寻找邻居,称为暴力搜索方法。它简单直接,但对于大规模数据集来说可能比较慢。

在这里,我们将探讨两种更高效的寻找最近邻居的算法:

KNN 回归的 KD 树

KD 树(K 维树)是一种二叉树结构,用于在k维空间中组织点。它特别适用于诸如最近邻搜索和多维数据范围搜索等任务。

训练步骤:

  1. 构建 KD 树:

a. 从包含所有点的根节点开始。

b. 选择一个特征进行划分。实际上,选择任何一个随机特征都可以,但另一种较好的选择方式是查看哪个特征的中位数值最接近最大值与最小值之间的中点。

温度具有最接近中位线的中点线。我们可以从该维度开始进行拆分。

c. 在选定的特征和中点处分割树。

d. 递归地执行步骤 a-c,直到达到停止标准,通常是最小叶子节点大小(见这里的“最小样本叶子”)

2. 存储目标值:

在 KD 树中的每个点旁边存储其对应的目标值。每个节点的最小值和最大值也被存储。

回归/预测步骤:

1. 遍历 KD 树:

a. 从根节点开始。

b. 比较查询点(测试点)与每个节点处的分割维度和值。

c. 根据比较结果递归地向左或向右遍历。

d. 当到达叶子节点时,将其点添加到候选集。

2. 精细化搜索:

a. 回溯树结构,检查其他分支是否有更接近的点。

b. 使用未探索分支的最大值和最小值进行距离计算,以确定是否需要继续探索。

我们回溯到未访问的分支,并检查那些节点的最小值和最大值的距离。

由于这些节点的最小值和最大值都比第 k 个距离远,因此无需检查这些节点中数据点的距离。

3. 查找 K 个最近邻:

a. 在找到的候选点中,选择与查询点最接近的 K 个点。

4. 执行回归:

a. 计算 K 个最近邻的目标值的平均值。

b. 这个平均值即为查询点的预测值。

通过使用 KD 树,找到最近邻的平均时间复杂度可以从暴力搜索法中的O(n)减少到在许多情况下的O(log n),其中n是数据集中的点数。这使得 KNN 回归在大数据集中的效率大大提高。

KNN 回归中的球树

球树是另一种空间划分数据结构,它通过一系列嵌套的超球面组织点。在高维数据中,KD 树可能变得低效,而球树在这种情况下尤为有效。

训练步骤:

1. 构建球树:

a. 计算节点中所有点的重心(均值)。这将成为枢纽点

b. 构建第一个分支:

  • 找到第一个中心: 选择距离枢纽点最远的点作为第一个中心,并将其距离作为半径

  • 寻找第二个中心: 从第一个中心出发,选择最远的点作为第二个中心。

  • 分区: 根据点更接近哪个中心将剩余的点分成两个子节点。

递归地对每个子节点应用步骤 a-b,直到满足停止标准,通常是最小叶子节点大小(请参见“最小样本叶子”)。

2. 存储目标值:

随着每个 Ball Tree 中的点,存储其对应的目标值。每个节点的半径和质心也会被存储。

回归/预测步骤:

1. 遍历 Ball Tree:

a. 从根节点开始。

b. 在每个节点上,计算未见数据与每个子超球中心之间的距离。

c. 首先遍历到最接近的超球。

d. 当到达叶节点时,将其点添加到候选集。

2. 精细化搜索:

a. 确定是否需要探索其他分支。

b. 如果到超球的距离加上其半径大于当前第 K 个最近距离,则可以安全忽略该分支。

对于之前考虑过的分支,将半径加到距离上。如果该距离大于当前的第 K 个距离,则无需探索这些球。

3. 查找 K 个最近邻:

a. 从找到的候选点中,选择与查询点最接近的 K 个点。

4. 执行回归:

a. 计算 K 个最近邻的目标值平均值。

b. 这个平均值就是查询点的预测值。

Ball Tree 对于高维数据或维度大于样本数量对数的情况比 KD Tree 更高效。即使维度增加,它们也能保持良好的性能,适用于广泛的数据集。

在 Ball Tree 中查询的时间复杂度通常是 O(log n),与 KD Tree 相似,但在高维度下,Ball Tree 通常表现得更好,而 KD Tree 可能退化为线性时间复杂度。

评估步骤(暴力法、KD 树、Ball 树)

无论我们选择哪种算法,它们都会给出相同的结果:

虚拟回归器的结果相比,RMSE 值有了显著改善。

选择哪种算法?

你可以遵循这个简单的规则来选择最优的算法:

  • 对于小型数据集(< 1000 样本),‘brute’可能足够快,并保证找到精确的最近邻。

  • 对于特征较少的大型数据集(< 20),‘kd_tree’通常是最快的。

  • 对于具有许多特征的大型数据集,‘ball_tree’通常表现最佳。

scikit-learn 中的‘auto’选项通常遵循以下图表:

关键参数

虽然 KNN 回归有许多其他参数,除了我们刚刚讨论的算法(暴力法、kd 树、ball 树),但你主要需要考虑的是

  1. 邻居数(K)

    • 较小的 K:对局部模式更敏感,但可能导致过拟合。

    • 较大的 K:平滑的预测,但可能错过重要的局部变化。

    与分类不同,回归中偶数也可以,因为我们是在对值进行平均。

  2. 叶节点大小

    这是构建 kd 树或 ball 树的停止条件。通常,它会影响构建和查询的速度,以及存储树所需的内存。

优缺点

优点:

  1. 简洁性和多功能性:易于理解和实现;可以用于分类和回归任务。

  2. 无假设:不对数据分布做任何假设,使其适用于复杂数据集。

  3. 无训练阶段:可以快速纳入新数据,而无需重新训练。

  4. 可解释性:通过检查最近邻,可以解释预测结果。

缺点:

  1. 计算复杂度:预测时间可能较慢,尤其是对于大数据集,尽管优化算法(KD-Tree、Ball Tree)在低维情况下能有所帮助。

  2. 维度灾难:在高维空间中,性能会下降,影响准确性和效率。

  3. 内存密集型:需要存储所有训练数据。

  4. 对特征缩放和无关特征敏感:可能会受到较大尺度特征或对预测无关特征的偏倚。

最终备注

K 最近邻(KNN)回归器是机器学习中一个基本而强大的工具。它简单直观,非常适合初学者,而且其灵活性确保它对专家同样有用。

随着你对数据分析的了解逐渐深入,可以使用 KNN 来理解回归的基础知识,然后再探索更高级的方法。通过掌握 KNN 和如何计算最近邻,你将为处理更复杂的数据分析挑战打下坚实的基础。

🌟 K 最近邻回归器代码概述

# Import libraries
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.metrics import root_mean_squared_error
from sklearn.neighbors import KNeighborsRegressor
from sklearn.preprocessing import StandardScaler
from sklearn.compose import ColumnTransformer

# Create dataset
dataset_dict = {
    'Outlook': ['sunny', 'sunny', 'overcast', 'rain', 'rain', 'rain', 'overcast', 'sunny', 'sunny', 'rain', 'sunny', 'overcast', 'overcast', 'rain', 'sunny', 'overcast', 'rain', 'sunny', 'sunny', 'rain', 'overcast', 'rain', 'sunny', 'overcast', 'sunny', 'overcast', 'rain', 'overcast'],
    'Temperature': [85.0, 80.0, 83.0, 70.0, 68.0, 65.0, 64.0, 72.0, 69.0, 75.0, 75.0, 72.0, 81.0, 71.0, 81.0, 74.0, 76.0, 78.0, 82.0, 67.0, 85.0, 73.0, 88.0, 77.0, 79.0, 80.0, 66.0, 84.0],
    'Humidity': [85.0, 90.0, 78.0, 96.0, 80.0, 70.0, 65.0, 95.0, 70.0, 80.0, 70.0, 90.0, 75.0, 80.0, 88.0, 92.0, 85.0, 75.0, 92.0, 90.0, 85.0, 88.0, 65.0, 70.0, 60.0, 95.0, 70.0, 78.0],
    'Wind': [False, True, False, False, False, True, True, False, False, False, True, True, False, True, True, False, False, True, False, True, True, False, True, False, False, True, False, False],
    'Num_Players': [52, 39, 43, 37, 28, 19, 43, 47, 56, 33, 49, 23, 42, 13, 33, 29, 25, 51, 41, 14, 34, 29, 49, 36, 57, 21, 23, 41]
}

df = pd.DataFrame(dataset_dict)

# One-hot encode 'Outlook' column
df = pd.get_dummies(df, columns=['Outlook'])

# Convert 'Wind' column to binary
df['Wind'] = df['Wind'].astype(int)

# Split data into features and target, then into training and test sets
X, y = df.drop(columns='Num_Players'), df['Num_Players']
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.5, shuffle=False)

# Identify numerical columns
numerical_columns = ['Temperature', 'Humidity']

# Create a ColumnTransformer to scale only numerical columns
ct = ColumnTransformer([
    ('scaler', StandardScaler(), numerical_columns)
], remainder='passthrough')

# Fit the ColumnTransformer on the training data and transform both training and test data
X_train_transformed = ct.fit_transform(X_train)
X_test_transformed = ct.transform(X_test)

# Convert the transformed data back to DataFrames
feature_names = numerical_columns + [col for col in X_train.columns if col not in numerical_columns]
X_train_scaled = pd.DataFrame(X_train_transformed, columns=feature_names, index=X_train.index)
X_test_scaled = pd.DataFrame(X_test_transformed, columns=feature_names, index=X_test.index)

# Initialize and train KNN Regressor
knn = KNeighborsRegressor(n_neighbors=5, 
                          algorithm='kd_tree', #'ball_tree', 'brute'
                          leaf_size=5) #default is 30
knn.fit(X_train_scaled, y_train)

# Make predictions
y_pred = knn.predict(X_test_scaled)

# Calculate and print RMSE
rmse = root_mean_squared_error(y_test, y_pred)
print(f"RMSE: {rmse:.4f}")

进一步阅读

有关KNeighborsRegressorKDTreeBallTree及其在 scikit-learn 中的实现的详细说明,读者可以参考其官方文档。它提供了关于这些工具的使用及参数的全面信息。

技术环境

本文使用的是 Python 3.7 和 scikit-learn 1.5 版本。虽然讨论的概念普遍适用,但不同版本之间的具体代码实现可能会略有不同。

关于插图

除非另有说明,所有图片均由作者创建,并融合了来自 Canva Pro 的授权设计元素。

𝙎𝙚𝙚 𝙢𝙤𝙧𝙚 𝙍𝙚𝙜𝙧𝙚𝙨𝙨𝙞𝙤𝙣 𝘼𝙡𝙜𝙤𝙧𝙞𝙩𝙝𝙢𝙨 𝙝𝙚𝙧𝙚:

Samy Baladram

Samy Baladram

回归算法

查看列表5 篇故事一只扎着辫子戴着粉红色帽子的卡通娃娃。这个“假人”娃娃,凭借其基本设计和心形装饰的衬衫,形象地展示了机器学习中的假回归器概念。就像这个玩具般的形象是一个简化的、静态的人物表达一样,假回归器也是一个基本模型,用作更复杂分析的基准。

𝙔𝙤𝙪 𝙢𝙞𝙜𝙝𝙩 𝙖𝙡𝙨𝙤 𝙡𝙞𝙠𝙚:

Samy Baladram

Samy Baladram

分类算法

查看列表8 篇故事

KAN:它为什么以及如何工作?深入探讨

原文:towardsdatascience.com/kan-why-and-how-does-it-work-a-deep-dive-1adab4837fa3?source=collection_archive---------0-----------------------#2024-05-10

我们能通过 KAN 发现新的物理学吗?

Saptashwa BhattacharyyaTowards Data Science Saptashwa Bhattacharyya

·发表于 Towards Data Science ·阅读时长 13 分钟·2024 年 5 月 10 日

--

神经网络能发现新的物理学吗?(由作者使用 DALLE-2 生成)

上周,在我们参加的一个 AI 与物理学会议(EuCAIFCon)上,很多讨论围绕基础模型展开,讨论是否有可能通过 AI 发现潜在的新物理定律,结果不出所料:最近有一篇关于 KAN 的论文¹在 arXiv 上发表,讨论了通过神经网络发现/重新发现物理学和数学模型的可能性。

我在周末有时间阅读了这篇令人着迷的论文的一部分,接下来我们将深入探讨构建 KAN 的数学、公式和背后的思想。我们还将安装 KAN,检查 MIT 研究人员在论文中展示的许多令人惊叹的结果之一。

我们将详细了解数学符号和概念,以便理解为什么这个网络引起了如此大的轰动!

让我们开始吧。

KA 表示定理:

KAN 或 Kolmogorov-Arnold 网络是基于著名数学家 Kolmogorov 和 Arnold 的表示定理。因此,首先让我们回顾一下这个定理。

KA 定理是由这些数学家提出,用于研究 Hilbert 的第十三问题:David Hilbert 提出有必要…

保持梯度流动

原文:towardsdatascience.com/keep-the-gradients-flowing-5b9bf0098e3d?source=collection_archive---------4-----------------------#2024-10-09

优化稀疏神经网络:理解梯度流动以加速训练、提高效率,并在深度学习模型中提升性能

Cristian LeoTowards Data Science Cristian Leo

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

--

人工智能图像生成,展示了神经网络中梯度流动的情况

近年来,人工智能领域一直痴迷于构建越来越大的神经网络,认为更高的复杂度会带来更好的性能。的确,这一方法已经取得了令人难以置信的成果,带来了图像识别、语言翻译以及其他许多领域的突破。

但这里有个问题。就像一个庞大且过于复杂的机器既昂贵又难以维护一样,这些巨大的神经网络也需要大量的计算资源和时间来训练。它们可能训练得很慢,需要大量的内存和计算能力,这使得在资源有限的设备上部署它们变得具有挑战性。此外,它们往往更容易“记住”训练数据,而不是真正理解潜在的模式,从而在未见数据上的表现较差。

稀疏神经网络在一定程度上解决了上述问题。可以将稀疏神经网络看作是经典神经网络的精简版。它们小心地移除不必要的部分和连接,从而形成一个更高效、更加精简的模型,同时仍保持其强大的能力。它们能够更快地训练,需求更少的内存,而且通常更具鲁棒性……

使用 DVC 的实验追踪功能来跟踪你的回测

原文:towardsdatascience.com/keep-track-of-your-backtests-with-dvcs-experiment-tracking-38977cbba4a9?source=collection_archive---------9-----------------------#2024-01-05

图片由 Midjourney 生成

本教程第四部分,讲解如何使用 DVC 进行实验追踪,这次是时间序列预测

Eryk LewinsonTowards Data Science Eryk Lewinson

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

--

在本系列的前几部分中,我演示了如何使用 DVC 进行实验追踪。只需稍微多一点努力,你就可以停止在纸上或电子表格中记录实验结果了。

你可以在下面找到本系列的前几部分:

  • 第一部分: 将 VS Code 打造成机器学习实验的一站式商店

  • 第二部分: 通过实时图表提升你的机器学习实验工作流

  • 第三部分: DVC 实验追踪的极简主义指南

在前几部分中,我们看到了一些分类问题的实验追踪示例。在时间序列预测中,我们使用不同的方法来验证模型。因此,我们可能还需要追踪更多关于回测的元数据,并保存多个聚合组的得分。

在我上一篇关于回测的文章中 回顾你的预测模型:回测指南,我创建了一个自定义类,使我能够轻松评估我的预测模型。然后,我在 Jupyter Notebook 中运行了多个回测,并将结果存储在一个大的字典中。尽管这种方法…

让你的 AI 代理保持受控:跟踪、指标和日志的入门指南

原文:towardsdatascience.com/keeping-your-ai-agent-in-check-an-introductory-guide-to-traces-metrics-and-logging-a731b57b8658?source=collection_archive---------8-----------------------#2024-11-14

了解如何实现指标、日志和集中监控,以保持你的 AI 代理稳健且准备好投入生产

Youness MansarTowards Data Science Youness Mansar

·发表于Towards Data Science ·10 分钟阅读·2024 年 11 月 14 日

--

图片来自Rostyslav Savchyn提供,Unsplash

构建 AI 代理是一个令人兴奋的挑战,但仅仅部署它们并不足以确保为用户提供顺畅、稳定的体验。一旦部署,AI 应用程序需要有效的监控和日志记录来保持其最佳运行状态。没有适当的可观察性工具,问题可能未被发现,即使是小的 bug 也可能演变成重大生产问题。

在本指南中,我们将带你了解如何为你的 AI 代理设置监控和日志记录,以便你能完全掌握其行为和性能。我们将探讨如何收集必要的指标,收集日志,并将这些数据集中在一个平台上。在本教程结束时,你将拥有一个基础设置,能够及早发现、诊断和解决问题,从而确保 AI 应用程序更加稳定和响应迅速。

完整代码可在这里获取:github.com/CVxTz/opentelemetry-langgraph-langchain-example

介绍关键工具:OpenTelemetry、New Relic 和 LangGraph

Keras 3.0 教程:端到端深度学习项目指南

原文:towardsdatascience.com/keras-3-0-tutorial-end-to-end-deep-learning-project-guide-3552187e3ff5?source=collection_archive---------1-----------------------#2024-05-18

从头实现一个编码器-解码器递归网络

Peng QianTowards Data Science Peng Qian

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

--

Keras 3.0 教程:端到端深度学习项目指南。图片由作者提供

介绍

尽管我在一段时间前就开始使用 Pytorch,但我仍然怀念 Keras 简洁的代码风格,以及那段你可以用几行代码就实现一个神经网络模型的美好时光。

所以,当 Keras 在去年 11 月宣布除了 TensorFlow 之外,它现在还支持 Pytorch 和 Jax 作为后端时,我感到非常兴奋!

然而,事情并不完美:由于 Keras 3.0 是最近发布的,相关的教程和文档还没有赶上,我在代码迁移过程中遇到了一些问题。

幸运的是,在一些努力之后,我现在可以顺利使用 3.0 版本进行各种端到端的模型开发。

在这篇文章中,我将分享一些使用 Keras 3.0 的实际经验,帮助你避免一些弯路。我将使用一个典型的编码器-解码器递归神经网络作为例子,解释如何使用 Keras 3.0 的子类化 API 从零开始完成一个端到端的项目,并讨论在使用 Pytorch 作为后端时需要考虑的细节。

让我们开始吧。

框架安装与环境设置

KernelSHAP 在预测变量相关时可能产生误导

原文:towardsdatascience.com/kernelshap-can-be-misleading-with-correlated-predictors-9f64108f7cfb?source=collection_archive---------7-----------------------#2024-08-09

一个具体的案例研究

Shuyang XiangTowards Data Science Shuyang Xiang

·发表于Towards Data Science ·阅读时长:7 分钟·2024 年 8 月 9 日

--

“像许多其他基于置换的解释方法一样,Shapley 值方法在特征相关时会遭遇不现实数据实例的引入。为了模拟一个特征值在一个联合体中缺失的情况,我们对该特征进行边际化处理……当特征之间存在依赖关系时,我们可能会抽取一些对于当前实例来说不合理的特征值。”—— 可解释机器学习书籍

SHAP(Shapley 加性解释)值旨在根据合作博弈论中的 Shapley 值概念,公平地分配每个特征对机器学习模型预测的贡献。Shapley 值框架具有几个理想的理论属性,并且原则上可以处理任何预测模型。然而,SHAP 值可能会产生误导,特别是在使用 KernelSHAP 方法进行近似时。当预测变量之间存在相关性时,这些近似值可能会不准确,甚至可能有相反的符号。

在这篇博客文章中,我将展示原始的 SHAP 值如何与SHAP 框架的近似值有显著差异,尤其是 KernelSHAP,并讨论这些差异背后的原因。

案例研究:客户流失率

考虑一个场景,我们旨在预测一个办公楼租赁的流失率,基于两个关键因素:入住率和报告问题的比例。

占用率对流失率有显著影响。例如,如果占用率过低,租户可能会因为办公室未被充分利用而离开。相反,如果占用率过高,租户可能会因为拥挤而离开,寻求更好的选择。

此外,我们假设报告问题的比率与占用率高度相关,具体而言,报告问题的比率是占用率的平方。

我们将流失率函数定义如下:

作者提供的图像:流失率函数

该函数对于这两个变量的表示可以通过以下插图表示:

作者提供的图像:关于两个变量的流失

原始 SHAP 与 Kernel SHAP 之间的差异

使用 Kernel SHAP 计算的 SHAP 值

我们将使用以下代码计算预测变量的 SHAP 值:

# Define the dataframe 
churn_df=pd.DataFrame(
    {
        "occupancy_rate":occupancy_rates,
        "reported_problem_rate": reported_problem_rates,
        "churn_rate":churn_rates,
    }
)
X=churn_df.drop(["churn_rate"],axis=1)
y=churn_df["churn_rate"]

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state = 42)
# append one speical point
X_test=pd.concat(objs=[X_test, pd.DataFrame({"occupancy_rate":[0.8], "reported_problem_rate":[0.64]})])

# Define the prediction
def predict_fn(data):
    occupancy_rates = data[:, 0]
    reported_problem_rates = data[:, 1]

    churn_rate= C_base +C_churn*(C_occ* occupancy_rates-reported_problem_rates-0.6)**2 +C_problem*reported_problem_rates
    return churn_rate

# Create the SHAP KernelExplainer using the correct prediction function
background_data = shap.sample(X_train,100)
explainer = shap.KernelExplainer(predict_fn, background_data)
shap_values = explainer(X_test)

上面的代码执行以下任务:

  1. 数据准备:创建一个名为churn_df的 DataFrame,包含occupancy_ratereported_problem_ratechurn_rate列。然后从中创建变量和目标(churn_rate),并将数据拆分为训练集和测试集,训练集占 80%,测试集占 20%。注意,测试集X_test中添加了一个具有特定occupancy_ratereported_problem_rate值的数据点。

  2. 预测函数定义:定义了一个函数predict_fn,使用涉及预定义常量的特定公式计算流失率。

  3. SHAP 分析:使用预测函数和来自X_trainbackground_data样本初始化一个 SHAP KernelExplainer。然后使用explainer计算X_test的 SHAP 值。

下面,您可以看到一个总结性的 SHAP 条形图,表示X_test的平均 SHAP 值:

作者提供的图像:平均 SHAP 值

特别地,我们看到在数据点(0.8,0.64)处,两个特征的 SHAP 值分别为 0.10 和-0.03,如下图所示的力图所示:

作者提供的图像:单一数据点的力图

原始定义的 SHAP 值

让我们退后一步,根据 SHAP 的原始定义逐步计算确切的 SHAP 值。SHAP 值的一般公式如下所示:

其中:S 是所有特征索引的子集,排除 i,|S|是子集 S 的大小,M 是特征的总数,f(XS∪{xi})是包含 xi 的 S 的特征所评估的函数,而 f(XS)是 S 中缺少 xi 时评估的函数。

现在,让我们计算两个特征的 SHAP 值:占用率(表示为 x1)和报告问题率(表示为 x2),它们在数据点(0.8,0.64)处的值。回想一下,x1 和 x2 之间的关系是 x1 = x2²。

我们得到了数据点处占用率的 SHAP 值:

同样地,对于报告问题率这一特征:

首先,让我们计算数据点处占用率的 SHAP 值:

  1. 第一项是当 X1 固定为 0.8 且 X2 在其分布上取平均时模型输出的期望值。由于 x1 = x2²的关系,这一期望值导致模型在特定点(0.8, 0.64)处的输出。

  2. 第二项是模型输出的无条件期望值,其中 X1 和 X2 都在其分布上取平均。可以通过对背景数据集中的所有数据点的输出进行平均来计算这一期望值。

  3. 第三项是模型在特定点(0.8, 0.64)处的输出。

  4. 最后一项是当 X1 在其分布上取平均时,给定 X2 固定在特定点 0.64 时模型输出的期望值。同样,考虑到 x1 = x2²的关系,这一期望值与模型在(0.8, 0.64)处的输出相符,类似于第一步。

因此,从原始定义计算的两个特征——占用率和报告问题率在数据点(0.8, 0.64)处的 SHAP 值分别为-0.0375 和-0.0375,这与 Kernel SHAP 给出的值有很大不同。

差异从何而来?

SHAP 值差异的原因

如你所注意到的,两种方法之间的差异主要出现在第二步和第四步,这两步需要计算条件期望。这涉及到在 X1 被固定为 0.8 时,计算模型输出的期望值。

  • 精确 SHAP:在计算精确的 SHAP 值时,特征之间的依赖关系(例如我们示例中的 x1=x_2²)被显式考虑。这通过考虑特征交互如何影响模型输出,确保了准确的计算。

  • Kernel SHAP:默认情况下,Kernel SHAP 假设特征之间是独立的,这在特征实际上是相关的情况下会导致不准确的 SHAP 值。根据论文A Unified Approach to Interpreting Model Predictions,这一假设是一个简化。在实际应用中,特征通常是相关的,这使得在使用 Kernel SHAP 时很难获得准确的近似值。

来自论文的截图

潜在的解决方案

不幸的是,基于原始定义直接计算 SHAP 值可能会非常耗费计算资源。以下是一些可供考虑的替代方法:

TreeSHAP

  • 专门为树模型(如随机森林和梯度提升机)设计的 TreeSHAP 高效地计算 SHAP 值,同时有效管理特征依赖关系。

  • 该方法针对树集成进行了优化,使其比传统的 SHAP 计算更快且更具可扩展性。

  • 在 SHAP 框架中使用 TreeSHAP 时,设置参数 feature_perturbation = "interventional" 以准确考虑特征之间的依赖性。

扩展 Kernel SHAP 以处理依赖特征

  • 为了解决特征之间的依赖性,本文扩展了 Kernel SHAP。一个方法是假设特征向量服从多元高斯分布。在这种方法中:

  • 条件分布被建模为多元高斯分布。

  • 样本是通过使用来自训练数据的估计,从这些条件高斯分布中生成的。

  • 该近似中的积分是基于这些样本计算的。

  • 该方法假设特征服从多元高斯分布,但在实际场景中,特征可能展现出不同的依赖结构,因此该假设并不总是适用。

提升 Kernel SHAP 的准确性

  • 描述:通过确保用于近似的背景数据集能够代表实际数据分布且特征独立,从而提高 Kernel SHAP 的准确性。

通过使用这些方法,您可以解决计算 SHAP 值时遇到的挑战,并提高它们在实际应用中的准确性。然而,需要注意的是,没有一种解决方案可以在所有场景中都适用。

结论

在这篇博文中,我们探讨了尽管 SHAP 值具有强大的理论基础并且在各种预测模型中具有广泛的适用性,但当预测变量之间存在相关性时,特别是在采用类似 KernelSHAP 这样的近似方法时,SHAP 值可能会出现准确性问题。理解这些局限性对于有效地解释 SHAP 值至关重要。通过识别潜在的差异并选择最合适的近似方法,我们可以在模型中实现更准确可靠的特征归因。

教授 AI 智能体记忆的关键洞察

原文:towardsdatascience.com/key-insights-for-teaching-ai-agents-to-remember-c23deffe7f1a?source=collection_archive---------10-----------------------#2024-09-10

关于基于对 Autogen“可教智能体”实验的记忆能力构建建议

Sandi BesenTowards Data Science Sandi Besen

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

--

记忆无疑正成为智能体 AI(Agentic AI)中的一个关键组成部分。随着 AI 智能体应用场景的复杂化,这些智能体需要从过去的经验中学习,利用存储的特定业务知识,并根据累积的信息适应不断变化的场景。

在我之前的文章“AI 中的记忆:关键好处与投资考量”中,我探讨了记忆为何对 AI 至关重要,讨论了它在回忆、推理和持续学习中的作用。然而,本文将直接深入记忆的实施,通过在流行智能体框架Autogen中考察其“可教性”功能来分析其影响。

注意: 尽管本文具有技术性,但它对技术专业人士和希望评估记忆在智能体 AI 系统中作用的商业领袖同样具有价值。我已将其结构化,以便读者可以跳过代码部分,依然能够理解记忆如何增强 AI 系统的响应。如果你不想深入跟随代码,你可以阅读每个步骤的描述,了解过程的概况……或者直接阅读主要发现与建议部分。

来源:Dalle3 ,提示作者:Sandi Besen

主要发现与建议

我对 Autogen 的可教智能体进行的探索揭示了它们在处理简单和复杂记忆任务时的潜力与局限性。

初步使用时,Autogen 的可教学代理的表现不如预期。该代理的推理能力将记忆混合在一起,产生了低效的结果,而且其内建的检索机制没有设置为适应回答复杂问题所需的多步搜索。这一局限性表明,如果你想使用 Autogen 的可教学代理,必须进行大量定制,以补充推理能力并实现更复杂的记忆检索。

为了构建更强大的记忆能力,实施多步搜索功能至关重要。单一的记忆搜索通常无法提供完成复杂任务所需的全面信息。实施一系列互相关联的搜索可以显著增强代理收集和综合相关信息的能力。

“可教学性”功能虽然强大,但应谨慎使用持续激活而没有监督会导致数据污染并危及可信信息源。商业领袖和解决方案架构师应考虑实施“人类在环”方法,让用户批准系统学习的内容,而不是将每一个推断视为系统应学习的真实事实。Autogen 当前的可教学代理设计中的这种监督缺失可能会带来与不受控制学习相关的重大风险。

最后,从知识库中进行记忆检索的方法在系统的有效性中起着重要作用。超越简单的最近邻搜索(这是 TeachableAgent 的默认方式),采用更先进的技术,如混合搜索(结合关键词和向量方法),语义搜索或知识图谱的使用可以显著提高检索信息的相关性和准确性

描述性代码实现

为了恰当地演示外部记忆如何发挥作用,我为一家汽车零部件制造厂创建了一个虚拟场景。按照下面的代码自行实现一个可教学代理。

场景:一家汽车零部件制造厂需要制定一个应急计划,以应对可能出现的能源限制。该计划需要灵活,并根据工厂能够使用的电力消耗量以及哪些零部件和型号的需求来调整。

步骤 1:

预先设置要求如果你的环境中没有安装 autogen,需要通过 pip 安装 autogen,并创建一个配置 JSON 文件。

一个兼容的配置文件示例,使用 Azure OpenAI 的服务模型 GPT4–o:

[{
    "model": "gpt-4o",
    "api_key": "<YOUR API KEY>",
    "azure_endpoint": "<YOUR ENDPOINT>",
    "api_type": "azure",
    "api_version": "2024-06-01"
}]

安装 Autogen for python:

pip install pyautogen

步骤 2:

将必要的库导入到你的笔记本或文件中,并加载配置文件。

import autogen
from autogen.agentchat.contrib.capabilities.teachability import Teachability
from autogen import ConversableAgent, UserProxyAgent

config_list = autogen.config_list_from_json(
    env_or_file="autogenconfig.json", #the json file name that stores the config 
    file_location=".", #this means the file is in the same directory
    filter_dict={
        "model": ["gpt-4o"], #select a subset of the models in your config
    },
)

步骤 3:

创建代理。由于 Autogen 框架的工作方式,我们需要创建两个代理。我们使用一个 UserProxyAgent 来执行任务,并与人类进行互动或替代人类参与(具体取决于所需的人工干预程度)。我们还创建一个 Conversable Agent 作为“可教代理”(Teachable Agent),它旨在与其他代理互动(而非用户)。你可以在这里了解更多关于UserProxyAgentsConversableAgents的信息。

teachable_agent = ConversableAgent(
    name="teachable_agent",  # the name can't contain spaces
    llm_config={"config_list": config_list, "timeout": 120, "cache_seed": None},  # in this example we disable caching but if it is enabled it caches API requests so that they can be reused when the same request is used
) 

user = UserProxyAgent(
    name="user",
    human_input_mode="ALWAYS", #I want to have full control over the code executed so I am setting human_input_mode to ALWAYS. Other options are NEVER and TERMINATE.
    is_termination_msg=lambda x: True if "TERMINATE" in x.get("content") else False, #setting a termination  message is VERY important because it tells the agent when to finish.
    max_consecutive_auto_reply=0, #we don't need this agent to replies multiple times in a row
    code_execution_config={
        "use_docker": False
    },  # If you are planning on running code look into setting use_docker=True. For this example I am not because then I have to walk through the docker setup, but it is safer than running the code directly.
)

步骤 4:

为了建立基准,让我们在“教导”代理之前和之后,向代理提出相同的问题。我故意展示了不同难度级别的问题,以展示在需要单步与多步记忆检索的任务上的表现。要成功解决复杂的多步问题,检索机制必须从两个不同的记忆中提供信息,作为上下文供负责生成最终回应的语言模型使用。

简单问题:“该设施正面临 40% 的电力短缺。需要优先处理哪些型号?”

复杂的多步问题:“该设施正面临 40% 的电力短缺。请为我提供一份详细的机器停用和保留的机器清单。”

正如预期的那样,回复是通用的,并不特别适用于个别业务。

启用长期记忆之前的回应:

SIMPLE QUESTION RESPONSE
===========================
>>>>>>>> USING AUTO REPLY...
teachable_agent (to user):

To address a power shortage of 30% effectively, it's crucial to prioritize models that are essential to critical operations, safety, and those that have the highest impact on productivity. Here are steps to determine which models to prioritize:

1\. **Identify Essential Systems:** Prioritize systems that are vital for safety, security, and essential operations. These might include:
   - Emergency lighting
   - Security systems (alarms, surveillance)
   - Critical environmental controls (e.g., refrigeration for hazardous materials)
   - Communication systems

2\. **Evaluate Impact on Core Operations:** Determine which models are crucial to the facility’s core functions. This can include machinery, servers, or any processes that are indispensable for maintaining productivity and operations.

3\. **Energy Efficiency:** Prioritize models and equipment that are more energy-efficient and can operate within the reduced power capacity.

4\. **Downtime Cost:** Consider the cost of downtime for each model. Prioritize those where downtime would result in significant financial losses or production delays.

Based on these principles, here’s a generic prioritization framework:

1\. **Safety and Security Systems:**
   - Emergency Lighting
   - Fire Alarms and Sprinkler Systems
   - Security Cameras and Sensors

2\. **Critical Operational Systems:**
   - Essential Production Machinery (that cannot be shut down without major issues)
   - Servers and IT Infrastructure (especially those critical for operations and data integrity)
   - HVAC Systems (if necessary for maintaining safe operating conditions)

3\. **Support Systems:**
   - Backup Power Systems (uninterruptible power supplies, generators)
   - Communication Systems (both internal and external communication)

Ensure that non-essential and high energy-consuming equipment is powered down or scheduled to operate during off-peak hours if possible.

Always re-evaluate periodically to ensure that the power allocation aligns with the dynamic needs of the facility. Communication with key stakeholders regarding priorities and decisions is also essential for smooth operations during a power shortage.

COMPLEX MULTISTEP QUESTION RESPONSE
====================================
>>>>>>>> USING AUTO REPLY...
teachable_agent (to user):

To effectively manage a facility experiencing a 30% power shortage, we need to prioritize essential operations and consider the criticality and energy consumption of each machine. Here is a detailed breakdown:

### Step 1: Identify Essential Operations

1\. **Critical Infrastructure**: Safety systems, security systems, and essential IT infrastructure.
2\. **Production Lines**: Machines involved in the primary production process.
3\. **Support Systems**: HVAC systems, lighting, and other ancillary equipment necessary to maintain a safe working environment.

### Step 2: Categorize Machines by Importance and Energy Consumption

#### A. Essential and High-Energy Machines

1\. **Safety and Security Systems**
   - Fire alarms and sprinklers
   - Surveillance cameras
   - Security access controls

2\. **Primary Production Machines**
   - Core production lines
   - Central processing units or mainframe servers (for IT-heavy operations)

3\. **Critical Support Systems**
   - Emergency lighting
   - Essential HVAC systems (for maintaining operation-critical temperature and humidity)

#### B. Non-Essential and High-Energy Machines

1\. **Secondary Production Lines**
   - Non-primary or non-critical production lines
   - Equipment used for non-essential or ancillary processes

2\. **Supplementary IT Infrastructure**
   - Servers used for non-essential operations
   - Backup systems that are not in immediate use

3\. **Non-Essential HVAC and Lighting**
   - HVAC systems for non-critical areas
   - Lighting in non-essential zones

#### C. Non-Essential and Low-Energy Machines

1\. **Office Equipment**
   - Computers, printers, and copiers in non-essential offices
2\. **Non-essential Lab or Research Equipment**
   - Devices not directly linked to current essential tasks

### Step 3: Prioritize Deactivation

#### Immediate Deactivation

1\. **Non-Essential and High-Energy Machines**
   - Secondary production lines
   - Non-essential backup servers

2\. **Non-Essential HVAC and Lighting**
   - HVAC in non-critical areas
   - Non-essential lighting

3\. **Non-Essential Office Equipment**
   - Printers, non-essential computers, and copiers

#### Limited Usage

1\. **Essential and High-Energy Machines**
   - Operate critical production lines at reduced capacity if possible
   - Implement rotational operation schedules for essential processes

2\. **Critical Support Systems**
   - Optimize HVAC settings (e.g., raising or lowering set points within acceptable ranges)
   - Reduce lighting to minimum safety standards

### Step 4: Implement Energy Conservation Measures

1\. **Optimize Operations**
   - Utilize energy-efficient settings
   - Implement energy-saving protocols and encourage staff to conserve energy

2\. **Scheduled Downtime**
   - Plan for downtime during off-peak hours for essential machinery to balance the load

3\. **Regular Monitoring**
   - Monitor energy consumption in real-time to make quick adjustments as needed

### Summary

#### Remain Active:
1\. Safety and security systems
2\. Main production lines
3\. Critical IT infrastructure
4\. Essential HVAC and lighting for operational areas

#### Deactivate or Limit Use:
1\. Non-essential production lines
2\. Redundant IT systems
3\. Non-critical HVAC and lighting
4\. Office equipment in non-essential areas

**Note**: These recommendations should be adjusted based on the specific operational requirements of your facility. Additionally, it could be beneficial to communicate these changes to all staff to ensure compliance and understanding.

提问基准问题的代码:

 #simple question
user.initiate_chat(teachable_agent, message="The facility is experiencing a power shortage of 40%. What models need to be prioritized?", clear_history=True)
#multistep complex question
user.initiate_chat(teachable_agent, message="The facility is experiencing a power shortage of 30%. Provide me a detailed breakdown of what machines should be deactivated and which machines should remain active.", clear_history=True)

步骤 5:

创建“教导能力”,然后将其添加到代理中。Teachability 类继承自 AgentCapabiliy 类,基本上允许你向代理添加可定制的功能。

Teachability 类有许多可选参数,可以进一步探索这里

默认的 Teachability 类是为代理添加长期记忆的一种快速便捷的方法,但在生产环境中使用时,可能需要进行定制化修改,正如在关键发现部分所概述的那样。它涉及将消息发送到一个分析代理(Analyzer Agent),该代理会评估用户消息是否适合存储和检索。分析代理寻找可能适用于未来类似任务的建议,然后总结并将任务与建议的对存储在一个二进制数据库中,作为代理的“记忆”。

teachability = Teachability(
    verbosity=0,  # 0 for basic info, 1 to add memory operations, 2 for analyzer messages, 3 for memo lists.
    reset_db=True, # we want to reset the db because we are creating a new agent so we don't want any existing memories. If we wanted to use an existing memory store we would set this to false.
    path_to_db_dir="./tmp/notebook/teachability_db", #this is the default path you can use any path you'd like
    recall_threshold=1.5,  # Higher numbers allow more (but less relevant) memos to be recalled.
    max_num_retrievals=10 #10 is default bu you can set the max number of memos to be retrieved lower or higher
)

teachability.add_to_agent(teachable_agent)

步骤 6:

现在,教导代理(teachable_agent)已经配置完成,我们需要向其提供我们希望代理“学习”(存储到数据库并从中检索)的信息。

根据我们的场景,我希望代理对设施有基本的理解,设施内容包括:

  • 制造工厂生产的组件类型

  • 组件需要制作的汽车型号类型

  • 每个组件使用的机器

此外,我还想提供一些关于设施优先事项的操作性指导,这取决于电力约束的程度。这包括:

  • 50%以上能源容量约束情况下的指导

  • 25-50%能源容量约束情况下的指导

  • 0-25%能源容量约束情况下的指导

business_info = """
# This manufacturing plant manufactures the following vehicle parts:
- Body panels (doors, hoods, fenders, etc.)
- Engine components (pistons, crankshafts, camshafts)
- Transmission parts
- Suspension components (springs, shock absorbers)
- Brake system parts (rotors, calipers, pads)

# This manufactoring plant produces parts for the following models:
- Ford F-150
- Ford Focus
- Ford Explorer
- Ford Mustang
- Ford Escape
- Ford Edge
- Ford Ranger

# Equipment for Specific Automotive Parts and Their Uses

## 1\. Body Panels (doors, hoods, fenders, etc.)
- Stamping presses: Form sheet metal into body panel shapes
- Die sets: Used with stamping presses to create specific panel shapes
- Hydraulic presses: Shape and form metal panels with high pressure
- Robotic welding systems: Automate welding of body panels and structures
- Laser cutting machines: Precisely cut sheet metal for panels
- Sheet metal forming machines: Shape flat sheets into curved or complex forms
- Hemming machines: Fold and crimp edges of panels for strength and safety
- Metal finishing equipment (grinders, sanders): Smooth surfaces and remove imperfections
- Paint booths and spraying systems: Apply paint and protective coatings
- Drying ovens: Cure paint and coatings
- Quality control inspection systems: Check for defects and ensure dimensional accuracy

## 2\. Engine Components (pistons, crankshafts, camshafts)
- CNC machining centers: Mill and drill complex engine parts
- CNC lathes: Create cylindrical parts like pistons and camshafts
- Boring machines: Enlarge and finish cylindrical holes in engine blocks
- Honing machines: Create a fine surface finish on cylinder bores
- Grinding machines: Achieve precise dimensions and smooth surfaces
- EDM equipment: Create complex shapes in hardened materials
- Forging presses: Shape metal for crankshafts and connecting rods
- Die casting machines: Produce engine blocks and cylinder heads
- Heat treatment furnaces: Alter material properties for strength and durability
- Quenching systems: Rapidly cool parts after heat treatment
- Balancing machines: Ensure rotating parts are perfectly balanced
- Coordinate Measuring Machines (CMMs): Verify dimensional accuracy

## 3\. Transmission Parts
- Gear cutting machines: Create precise gear teeth on transmission components
- CNC machining centers: Mill and drill complex transmission housings and parts
- CNC lathes: Produce shafts and other cylindrical components
- Broaching machines: Create internal splines and keyways
- Heat treatment equipment: Harden gears and other components
- Precision grinding machines: Achieve extremely tight tolerances on gear surfaces
- Honing machines: Finish internal bores in transmission housings
- Gear measurement systems: Verify gear geometry and quality
- Assembly lines with robotic systems: Put together transmission components
- Test benches: Evaluate completed transmissions for performance and quality

## 4\. Suspension Components (springs, shock absorbers)
- Coil spring winding machines: Produce coil springs to exact specifications
- Leaf spring forming equipment: Shape and form leaf springs
- Heat treatment furnaces: Strengthen springs and other components
- Shot peening equipment: Increase fatigue strength of springs
- CNC machining centers: Create precision parts for shock absorbers
- Hydraulic cylinder assembly equipment: Assemble shock absorber components
- Gas charging stations: Fill shock absorbers with pressurized gas
- Spring testing machines: Verify spring rates and performance
- Durability test rigs: Simulate real-world conditions to test longevity

## 5\. Brake System Parts (rotors, calipers, pads)
- High-precision CNC lathes: Machine brake rotors to exact specifications
- Grinding machines: Finish rotor surfaces for smoothness
- Die casting machines: Produce caliper bodies
- CNC machining centers: Mill and drill calipers for precise fit
- Precision boring machines: Create accurate cylinder bores in calipers
- Hydraulic press: Compress and form brake pad materials
- Powder coating systems: Apply protective finishes to calipers
- Assembly lines with robotic systems: Put together brake components
- Brake dynamometers: Test brake system performance and durability

"""

business_rules_over50 = """
- The engine components are critical and machinery should be kept online that corresponds to producing these components when capacity constraint is more or equal to 50%: engine components
- Components for the following models should be prioritized when capacity constraint is more or equal to 50%: 1.Ford F-150
"""

business_rules_25to50 = """
- The following components are critical and machinery should be kept online that corresponds to producing these components when capacity constraint is between 25-50%: engine components and transmission parts 
- Components for the following models should be prioritized when capacity constraint is between 25-50%: 1.Ford F-150 2.Ford Explorer
"""

business_rules_0to25 = """
- The following components are critical and machinery should be kept online that corresponds to producing these components when capacity constraint is between 0-25%: engine components,transmission parts, Brake System Parts
- Components for the following models should be prioritized when capacity constraint is between 0-25%: 1.Ford F-150 2.Ford Explorer 3.Ford Mustang 4.Ford Focus
"""
user.initiate_chat(teachable_agent, message=business_info, clear_history=True)
user.initiate_chat(teachable_agent, message=business_rules_over50, clear_history=True)
user.initiate_chat(teachable_agent, message=business_rules_25to50, clear_history=True)
user.initiate_chat(teachable_agent, message=business_rules_0to25, clear_history=True)

步骤 7:

现在我们已经将重要信息添加到长期记忆中,让我们再问代理相同的问题,并查看它的回答有什么不同。

简单问题:“设施目前面临 40%的电力短缺。需要优先考虑哪些型号?”

复杂的多步骤问题:“设施目前面临 40%的电力短缺。请提供一份详细的机器停用和应保持活动机器的列表。”

实施长期记忆后的结果:

可教代理能够部分回答简单问题。它在需要优先考虑的汽车型号上是正确的,但提供了关于哪些组件需要包括的额外错误信息。

复杂的多步骤问题未能得到充分解决。它错误地将 0-25%和 25-50%容量限制的两个不同业务指南合并为一个泛化的指南,导致了不准确的回答。此外,它仅提供了每个组件生产中应保持活动或应停用的部分机器列表,遗漏了完整操作协议中的关键细节。这表明从多个独立的记忆源中准确检索和综合信息来回答复杂问题的挑战。

SIMPLE QUESTION RESPONSE
===========================
>>>>>>>> USING AUTO REPLY...
teachable_agent (to user):
Given the facility is experiencing a power shortage of 40%, this falls within the 25-50% capacity constraint range. According to your previously established priorities for such a scenario, the following models and components need to be prioritized:

### Components to Prioritize
- Engine components
- Transmission parts
- Brake System Parts

### Models to Prioritize
1\. Ford F-150
2\. Ford Explorer

You should ensure that the machinery necessary to produce these critical components for the Ford F-150 and Ford Explorer is kept online.

COMPLEX MULTISTEP QUESTION RESPONSE
====================================
>>>>>>>> USING AUTO REPLY...
teachable_agent (to user):

In response to the 40% power shortage, let's create a detailed plan to ensure the most critical machinery remains operational while deactivating less essential ones. 

### Critical Prioritization (0-50% capacity constraint):
- **Critical Components**:
  - Engine components (pistons, crankshafts, camshafts)
  - Transmission parts
  - Brake System Parts (rotors, calipers, pads)

- **Prioritized Models**:
  - Ford F-150
  - Ford Explorer

### Machines to Remain Active:
1\. **Engine Component Machines**:
   - Engine Block Milling Machine
   - Crankshaft Grinding Machine
   - Camshaft Milling Machine

2\. **Transmission Part Machines**:
   - Transmission Housing Caster
   - Gear Cutting Machine
   - Transmission Assembly Line

3\. **Brake System Part Machines**:
   - Brake Rotor Casting Machine
   - Brake Caliper Milling Machine
   - Brake Pad Press

### Machines to Deactivate:
1\. **Body Panel Machines**:
   - Door Stamping Machine
   - Hood Assembly Machine
   - Fender Bending Machine

2\. **Suspension Component Machines**:
   - Coil Spring Forming Machine
   - Shock Absorber Assembly Line

### Detailed Breakdown:

#### Active Machines:
- **Engine Block Milling Machine**:
  - Produces critical engine blocks, essential for Ford F-150 and Ford Explorer.
- **Crankshaft Grinding Machine**:
  - Ensures high precision crankshafts; crucial for the performance of Ford F-150 and Ford Explorer.
- **Camshaft Milling Machine**:
  - Produces camshafts, necessary for Ford F-150 and Ford Explorer engines.

- **Transmission Housing Caster**:
  - Important for casting the housing of transmissions used in Ford F-150 and Ford Explorer.
- **Gear Cutting Machine**:
  - Precision gears production, vital for Ford F-150 and Ford Explorer transmissions.
- **Transmission Assembly Line**:
  - Assembly of complete transmission units for Ford F-150 and Ford Explorer.

- **Brake Rotor Casting Machine**:
  - Produces brake rotors, crucial for the safe braking systems in Ford F-150 and Ford Explorer.
- **Brake Caliper Milling Machine**:
  - Machining of brake calipers, essential for Ford F-150 and Ford Explorer.
- **Brake Pad Press**:
  - Manufacturing of brake pads, necessary for ensuring braking efficiency in Ford F-150 and Ford Explorer.

#### Deactivated Machines:
- **Door Stamping Machine**:
  - While important, body panels like doors are less critical than engine and transmission components under current constraints.
- **Hood Assembly Machine**:
  - Deactivating hood assembly to free up power for critical components manufacturing.
- **Fender Bending Machine**:
  - Non-critical compared to engine and brake components during a significant power shortage.
- **Coil Spring Forming Machine**:
  - Suspension springs are lower priorities under current circumstances.
- **Shock Absorber Assembly Line**:
  - Suspension components aren’t critical enough to keep operational during this level of power shortage.

This plan ensures that production focuses on maintaining the critical components needed for primary models, the Ford F-150 and Ford Explorer, thereby maximizing the utility of the remaining power while managing production constraints effectively.

代码:

#simple question
user.initiate_chat(teachable_agent, message="The facility is experiencing a power shortage of 40%. What models need to be prioritized?", clear_history=True)
#multistep complex question
user.initiate_chat(teachable_agent, message="The facility is experiencing a power shortage of 30%. Provide me a detailed breakdown of what machines should be deactivated and which machines should remain active.", clear_history=True)

结论

虽然 Autogen 提供了一个关于具有记忆的 AI 系统的直接介绍,但在有效处理复杂任务方面仍显不足。

在开发具有记忆能力的 AI 代理系统时,请考虑专注于以下关键能力

  • 实施多步骤搜索以确保全面和相关的结果。这使得代理能够评估搜索结果的有效性,并利用检索到的信息解决查询的各个方面。此外,考虑使用更先进的检索方法,例如语义搜索、混合搜索或知识图谱,以获得最佳结果。

  • 为了限制数据中毒的潜力,制定一个深思熟虑的方法,决定谁可以“教”代理以及何时代理应“学习”。根据业务或开发者设定的指南,还可以利用代理推理来判断是否应由谁将某些内容添加到记忆中。

  • 通过添加记忆衰减机制,可以消除检索过时信息的可能性,该机制可判断何时记忆不再相关或何时需要用更新的记忆替换它。

  • 对于多智能体系统,涉及群聊或智能体之间的信息共享时,探索各种通信模式。确定传递补充知识的最有效方法,并设定限制以防止信息过载。

注:本文中表达的观点仅代表作者个人意见,并不一定反映其所属单位的观点或政策。

如果你还有问题,或者认为某些内容需要进一步澄清,欢迎在Linkedin给我发私信!我总是乐于进行深思并不断完善我的工作。

机器学习中的欺诈预测项目关键角色

原文:towardsdatascience.com/key-roles-in-a-fraud-prediction-project-with-machine-learning-565a01c98274?source=collection_archive---------17-----------------------#2024-06-11

开发欺诈预测的机器学习模型需要哪些角色?

Mahsa EbrahimianTowards Data Science Mahsa Ebrahimian

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

--

图片来自Marvin MeyerUnsplash

在开发工业界的机器学习项目时,数据科学家和机器学习工程师通常是最突出的角色。然而,现实情况是,要交付产品需要集体协作。在上一篇文章中,我们讨论了使用机器学习开发欺诈预测产品的步骤。在本文中,我们将探讨此类项目中的各种角色以及每个角色如何为项目的成功做出贡献。免责声明:并非所有项目都会有与下面列出的确切职务相符的团队或个人;根据公司结构,一个人可能会兼任多个角色并履行多个职能。这里,我根据自己参与不同欺诈预测项目的经验,概述了这个结构。

项目经理

项目经理的角色既关键又充满挑战。他们负责项目的规划和执行。在项目开始时,他们帮助定义计划并根据相关方的要求和技术团队的能力设定截止日期。在项目过程中,他们持续监控进度。如果实际任务或交付情况偏离计划,他们需要及时提出并与团队协调。因此,他们大部分时间都在与不同的团队、高层经理和业务相关方进行沟通。他们工作的两个主要挑战是:

  • 技术团队之间的相互依赖:这使得角色充满挑战,因为一个团队的输出(例如,数据工程师负责摄取数据)作为另一个团队的输入(例如,数据科学家使用这些数据)。第一步的任何延迟或变化都会影响到第二步。项目经理虽然通常不具备过多的技术背景,但需要意识到这些变化,并确保团队之间的有效沟通。

  • 竞争的业务优先级:业务相关方通常会改变优先级,或者不同团队之间可能会有需要对齐的竞争优先级。项目经理必须应对这些变化,并协调各个团队保持项目的进度。

通过有效管理这些挑战,项目经理在机器学习项目的成功交付中发挥了关键作用。

欺诈分析师

欺诈分析师的领域专业知识和经验对欺诈预测模型的开发和评估至关重要。从项目开始时,他们就提供有关当前欺诈趋势、常见欺诈场景、警示信号以及例外或“绿旗”的见解。数据科学家在特征创建/工程阶段将这些知识融入其中。一旦模型投入生产运行,就需要持续监控以保持或提高性能。在这一阶段,欺诈分析师在识别模型的真阳性或假阳性方面至关重要。此识别可以通过对客户历史的详细调查或通过联系客户确认来进行。欺诈分析师的反馈对于反馈循环过程是不可或缺的。

高层管理(合规、战略、营销等的首席官员)

高层管理者和 C 级高管在机器学习/人工智能欺诈项目的成功中起着至关重要的作用。他们的支持对于消除障碍和在项目战略方向上达成共识至关重要。因此,他们需要定期了解项目的进展,以便根据项目的具体需求支持在必要团队、工具和流程上的投资,并确保适当的资源得到分配。此外,他们还负责确保内部和外部各方对数据隐私及符合行业标准的合规性承担责任。通过培育问责文化并提供清晰的领导,他们帮助确保项目实现目标并与组织的整体战略顺利整合。他们的参与对于解决任何监管问题、管理风险以及推动项目成功实施和长期可持续性至关重要。

数据工程师

数据工程师为我们(数据科学家)提供构建模型所需的数据,这是任何机器学习项目中的关键步骤。他们负责设计和维护数据管道,无论是实时数据流还是数据仓库中的批处理流程。数据工程师从项目开始时就参与其中,识别数据需求、数据来源、处理需求以及数据可访问性的服务水平协议(SLA)要求。

他们构建管道以收集、转换和存储来自不同来源的数据,基本上处理 ETL 过程。他们还管理和维护这些管道,解决可扩展性要求、监控数据质量、优化查询和流程以提高延迟,并降低成本。

数据科学家

从理论上讲,数据科学家创建机器学习算法以预测业务的各种信息。实际上,我们在一天中的工作角色是多种多样的。我们首先识别业务问题,理解数据和可用资源,并定义解决方案,将其转化为技术要求。

数据科学家与数据工程师和 MLOps 工程师紧密合作,以实施解决方案。我们还与业务利益相关者合作,传达结果并获取反馈。模型评估是另一项关键责任,涉及选择合适的指标来评估模型的性能,持续监控和报告其表现,并留意性能的衰退。

持续改进的过程是数据科学家角色的核心,确保模型在时间推移中保持准确性和相关性。

Mlops 工程师

一旦数据工程师和数据科学家构建了数据管道和模型,就该将模型投入生产环境。MLOps 工程师在这一阶段发挥着至关重要的作用,他们弥合了开发和运维之间的鸿沟。在欺诈预测的背景下,时效性至关重要,因为业务需要在欺诈发生之前进行防范,这就要求管道过程在不到一秒钟的时间内完成。因此,MLOps 工程师确保模型能够无缝集成到生产环境中,保持可靠性和可扩展性。MLOps 工程师设计和管理部署模型所需的基础设施,实施持续集成和持续部署(CI/CD)管道,并实时监控模型的性能。他们还负责版本控制、自动化测试和模型重新训练过程,以确保模型保持最新。通过解决这些运营挑战,MLOps 工程师能够实现机器学习模型的顺利高效部署,确保它们为业务提供一致且有价值的结果。

最后一句话!

我们讨论了我在工作经验中识别的角色。这些角色根据项目的不同阶段和每个公司的具体情况有所不同。在我看来,在项目的初期,欺诈分析师、高层管理人员和数据科学家会共同合作,定义战略和需求。数据科学家在识别业务问题方面发挥着重要作用。他们与 MLOps 和工程团队合作,将业务问题转化为技术解决方案。数据工程师需要参与进来,讨论所需的管道开发。一个常见的挑战是,当这些团队之间缺乏沟通时,问题往往会在执行阶段显现出来。这可能会影响时间表和交付物的质量。因此,团队之间的整合度越高,实施和交付就会越顺利。

请在下方评论关于你公司中的角色。你的经验中有什么不同吗?

启动你的数据科学之旅——数据科学家入门指南

原文:towardsdatascience.com/kickstart-your-data-science-journey-a-guide-for-aspiring-data-scientists-96e5072bd19a?source=collection_archive---------3-----------------------#2024-11-06

启动数据科学职业生涯所需的关键技术技能

Saankhya MondalTowards Data Science Saankhya Mondal

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

--

你对数据科学感到好奇吗?数学和人工智能让你兴奋吗?你想探索数据科学并计划从事数据科学职业吗?无论你不确定从哪里开始,还是刚刚迈出数据科学的第一步,你都来对地方了。相信我,这份指南将帮助你自信地迈出第一步!

非会员可以点击这里阅读完整文章。

数据科学是最令人兴奋的工作领域之一。它是一个多学科的领域,结合了各种技术和工具来分析复杂的数据集,构建预测模型,并在商业、研究和技术中指导决策。

数据科学应用于多个行业,如金融、医疗、社交媒体、旅游、电子商务、机器人、军事和间谍活动。

使用 GPT 4o 生成的图像

关于数据科学的神话与真相

互联网上有大量关于如何开始数据科学的资料,这也导致了关于数据科学的神话和误解。最重要的两个误解是——

知识增强型代理在互动文本游戏中的应用

原文:towardsdatascience.com/knowledge-enhanced-agents-for-interactive-text-games-359e57da5de3?source=collection_archive---------17-----------------------#2024-01-10

用知识增强型 AI 代理革命化互动文本游戏

Prateek ChhikaraTowards Data Science Prateek Chhikara

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

--

简介:

通过自然语言进行交流对机器智能至关重要[9]。计算语言模型(LM)的最新进展使得在有限交互的任务上,如问答和程序化文本理解,取得了显著的性能[10]。认识到互动性是交流的一个关键方面,研究界将目光投向了在互动小说(IF)环境中训练和评估代理,比如基于文本的游戏,这为研究语言模型的推理能力以及人工智能(AI)代理在受限环境中执行多步骤现实任务的潜力提供了独特的测试平台。例如,在图 1 中,代理需要在客厅采摘水果,并将其放入厨房的蓝色盒子中。在这些游戏中,代理通过基于文本的输入在复杂环境中进行导航,这要求 AI 代理对自然语言和战略决策做出深刻理解。要在这些游戏中取得成功,代理必须管理其知识、推理并生成基于语言的行动,从而在游戏世界中产生预期且可预测的变化。

图 1. 互动小说(IF)游戏的示意图,其中一个代理需要完成采摘水果(例如苹果)并将其放入厨房的蓝色盒子中的任务。

背景与动机:

先前的研究表明,基于强化学习和语言模型的代理在 IF 环境中推理或解释科学概念时存在困难[1],这引发了关于这些模型是否能够在训练过程中观察到的情境之外的未知情境中进行泛化的问题[2]。例如,虽然像‘获取已知物质的熔点(或沸点)’这样的任务可能相对简单,但‘在特定环境中确定未知物质的熔点(或沸点)’对这些模型来说却可能具有挑战性。为了提高泛化能力,结合世界知识(例如,关于物体的可用性知识)可能是有效的,但迄今为止没有相关研究探索这一方向。此外,现有模型在从环境反馈中有效学习方面仍然存在困难。例如,在检查特定物质的导电性时,代理必须理解它已经获得了必要的电线和特定物质,才能继续寻找电源。因此,亟需一个框架,能够分析和评估不同类型的知识及其注入方法对基于文本的游戏代理的有效性。

我们的论文《增强知识的交互式文本游戏代理》提出了一种新颖的框架,旨在提升 AI 代理在这些 IF 环境中的表现。

已发布版本: dl.acm.org/doi/10.1145/3587259.3627561

我们很高兴地宣布,我们的论文在 KCAP 2023 会议上获得了最佳学生论文奖,这是我们团队创新研究和奉献精神的体现。🏆🏆🏆

核心创新——知识注入框架:

我们的工作提出了一个独特的框架,旨在增强 AI 代理的特定知识。该框架包含两个关键组件:

  1. 正确行为的记忆 (MCA): 该特性使得 AI 代理能够记住并利用过去的正确行为。通过保持对过去有效行为的记忆,代理可以制定更有效的策略,避免重复犯错。MCA 由环境反馈决定。如果一个行为获得了奖励,那么它就被认为是正确的。因此,正确的行为不能一开始就直接提供给代理,而是随着代理在(训练/测试时间)回合的进行,逐步存储到记忆中。

  2. 可用性知识 (Aff): 理解游戏世界中与物体的潜在互动至关重要。我们期望可用性能够通过列出与周围物体的可能互动,帮助模型更好地学习。与历史知识不同,环境并不提供这些可用性信息,而是需要从外部资源中获取。为此,我们使用 ConceptNet,并获取其关于给定 IF 游戏回合中物体的 capableOfusedFor 关系。

我们在两种 AI 代理架构中实现了该框架:

  1. 通过奖励的在线策略优化 (RL 方法)

  2. 单步离线预测(LM 方法)

1. 在线策略优化通过奖励(RL 方法)

纯 RL 基础模型 — DRRN 3

基线 DRRN 模型仅使用观察、库存和任务描述的输入来计算每个动作的 Q 值。为了增强 DRRN 基线,我们将外部知识注入到模型中,并创建了 DRRN 的三种新变体:

  1. aff: 通过使用一个独特的 GRU 编码层,我们将输入中的物体可用性引入到基线模型中。

  2. mca: 在该模型中,使用独立的 GRU 编码层将所有先前正确的动作传递给基线模型。

  3. aff ⊕ mca: 该架构的编码由代理的先前正确动作和物体可用性作为独立组件组成。

图 2:DRRN 架构,增强了先前正确动作和物体可用性的记忆。

RL 增强型 KG 模型 — KG-A2C [4] (图 3)

作为基线,我们使用 KG-A2C 的修改版,在该版本中,我们利用环境提供的单一黄金动作序列作为目标,尽管可能存在多个可能的黄金序列。我们发现这个目标的表现优于原始的预测有效动作目标。我们设计了以下知识注入策略来整合

KG-A2C 的正确动作和物体可用性知识记忆:

  1. mca: 在基线的基础上,我们将所有先前正确的

    动作上,通过使用一个独立的 GRU 编码层并将其与

    输出向量与其他输出表示一起。

  2. aff: KG-A2C 模型中的 KG 组件为我们提供了一个方便的方式来添加更多知识。特别是,我们直接将物体可用性知识作为附加三元组添加到 KG 中,位于

    基线模型。例如,给定 KG 中的现有关系

    (客厅,hasA,苹果)我们可以添加物体可用性关系:(苹果,

    使用了 For,eating)。通过这种方式,KG 编码网络可以生成

    游戏状态的更有意义的表示,并可能

    引导模型生成更好的动作。在我们的实验中,我们

    将这种方法与使用

    独立的 GRU 编码层,类似于 DRRN 的情况。

  3. aff ⊕ mca: 我们将 KG 中的物体可用性和所有

    先前的修正动作使用独立的 GRU 编码层。

图 3:集成了物体可用性和先前正确动作的 KG-A2C 模型架构。

2. 单步离线预测(LM 方法)

预训练语言模型 — RoBERTa 5

在这里,我们将任务视为多选题问答。在每一步,当前的游戏状态被视为问题,模型必须从候选动作集合中预测下一个动作。类似于强化学习(RL)智能体,模型在每一步都会接收到环境观察(𝑜𝑏𝑣)、库存(𝑖𝑛𝑣)和任务描述(𝑑𝑒𝑠𝑐)。然后,我们将其与每个动作拼接在一起,让语言模型选择得分最高的动作。由于可能的动作集合非常庞大,在训练过程中我们仅随机选择𝑛=4 个干扰动作,以减少计算负担,语言模型通过交叉熵损失进行训练,选择正确的动作。在推理时,模型会为所有有效动作分配分数,我们使用 top-p 采样来选择动作,以防止模型陷入动作循环。我们为基准 RoBERTa 模型提出了三种知识注入策略。

  1. mca: 在这里,我们通过将过去的正确动作列出为一个字符串并附加到原始输入中,使得语言模型(LM)能够意识到自己的过去正确动作。由于 RoBERTa 的 token 限制,我们使用一个大小为𝐴=5 的滑动窗口,即在每一步,模型最多只能看到过去的

    𝐴个正确动作。

  2. aff: 我们通过首先在一个包含对象效用的常识知识图子集上对语言模型进行适应,向模型注入效用知识。我们通过一个辅助的问答任务来对模型进行适应,遵循之前的知识注入工作[6]。由于效用知识三元组的数量庞大,无法简单地将其拼接到 RoBERTa 的输入中,因此我们使用预训练而非简单拼接输入。通过辅助问答任务对效用进行预训练可以缓解这一挑战,同时仍能使模型学习到相关知识。接着,我们在增强效用的模型基础上对我们的任务模型进行微调,如基准中所述。

  3. aff ⊕ mca: 这种变体仅仅是将 mca 和 aff 结合在一起。

图 4:使用干扰项训练的 RoBERTa 架构。

指令调优语言模型 — Flan T5 [7][8](图 5)

Swift 模型本身集成了前十个动作的历史背景。值得注意的是,与之前检查过的三个仅考虑最后十个正确动作历史的模型不同,Swift 模型遵循其原始设计,涵盖了前十个动作的完整历史。为了建立一个可与之前三种架构中应用的方法相比较的基准模型,我们从 Swift 模型中省略了动作历史。未做更改的 Swift 变体在这里被表示为mca版本。此外,将可供性融入基准模型中,形成了aff model。类似地,将可供性融入 mca 版本后,形成了aff ⊕ mca模型。这些可供性被引入主输入序列,紧随库存数据之后,并位于已访问房间信息之前。

图 5:以 Seq2Seq 方式训练的 Swift 架构。

实验设置

环境: 我们使用了 ScienceWorld [1],这是一个基于文本的复杂虚拟世界,呈现为英文。它拥有 10 个互联的地点,并包含 218 个独特的物品,包括各种仪器、电气组件、植物、动物以及家具和书籍等日常物品。游戏提供了丰富的互动,具有 25 种高级动作和每步最多 200,000 种可能的组合,尽管其中只有少数是实际有效的。ScienceWorld 包含 10 个任务,共有 30 个子任务。由于 ScienceWorld 的多样性,每个任务作为一个独立的基准,具有不同的推理能力、知识要求,并且需要完成目标状态的动作数不同。此外,每个子任务都有一组必须完成的目标(例如,专注于一个非生物物体并将其放入厨房的红色盒子中)。为了实验目的,我们从每个任务中选择了一个代表性的子任务。任务细节见附录(文章末尾)。

奖励与评分系统: ScienceWorld 的奖励系统旨在引导智能体朝着优选的解决方案前进。每个动作执行后,环境都会提供一个数值评分和一个布尔指示器,显示任务是否完成。智能体每个回合最多可以执行 100 步(动作)。最终得分介于 0 到 100 之间,反映了智能体在完成回合目标和子目标方面的表现。当智能体完成任务或达到 100 步限制时,回合结束,并计算累计得分。

实验见解:

  • 知识注入有助于文本游戏中的智能体——在 40 个案例中的 34 个中,我们的知识注入策略优于基准模型。

  • 可用性知识比正确行动的记忆更有益——可用性模型在 15 个案例中获得了最佳结果,其次是包括 MCA(8 个案例)。将这两种知识类型结合在一起在 11 个案例中取得了最佳结果。

  • 就任务的整体影响而言,语言模型变体 RoBERTa 和 Swift 从包含可用性知识中获益最大,分别相对于基线提高了 48%和 8%。图 6 中展示了一个例子,说明了语言模型在加入可用性知识后受益匪浅。

图 6:可用性模型在任务 4 中采取的行动。蓝色=步骤索引,绿色=累积得分,黄色=正确的行动。

  • 在任务中的变动效果取决于注入知识与任务的相关性——任务中的变动效果通常是由于注入知识与当前任务的相关性,某些任务(例如电力)从知识注入中受益更多。

  • 通过知识图谱注入可用性是最有效的;将其作为原始输入加入会增加模型的学习复杂度——我们探索了将可用性知识注入到 KG-A2C 的多种变体(图 7):通过将其作为输入添加到观察、库存和描述中,为可用性创建一个单独的 GRU 编码层,并将可用性添加到知识图谱本身。我们在三个子任务上评估了每种方法的表现:简单、中等和困难。

图 7:在 KG-A2C 中添加可用性的五种方法的效果。

结论性思考:

我们的研究代表了朝着更复杂的 AI 智能体迈出的重要一步。通过赋予它们从过去的行动中学习并深刻理解环境的能力,我们为 AI 铺平了道路,使其能够在各种生活场景中智能、直观地进行游戏和互动。该框架可以扩展到其他 AI 应用,如虚拟助手或教育工具,在这些应用中,理解和与环境互动至关重要。

大型语言模型的少样本提示最近在推理任务中显示出了潜力,同时互动交流和输入澄清也带来了明显的好处。探索它们在互动任务中的作用,无论是作为需要较少训练数据的解决方案,还是作为能够为知识蒸馏生成合成数据的组件,都是一个有前景的未来方向。

如果你喜欢我们的工作,请引用它 😁

@inproceedings{chhikara,
author = {Chhikara, Prateek and Zhang, Jiarui and Ilievski, Filip and Francis, Jonathan and Ma, Kaixin},
title = {Knowledge-Enhanced Agents for Interactive Text Games},
year = {2023},
doi = {10.1145/3587259.3627561},
booktitle = {Proceedings of the 12th Knowledge Capture Conference 2023},
pages = {157–165},
numpages = {9},
series = {K-CAP '23}
}

参考文献

[1] Ruoyao Wang, Peter Alexander Jansen, Marc-Alexandre Côté, 和 Prithviraj Ammanabrolu. 2022. ScienceWorld: 你的智能体比五年级学生更聪明吗?EMNLP(2022)。

[2] Peter Jansen, Kelly J. Smith, Dan Moreno, 和 Huitzilin Ortiz. 2021. 在多跳推理中评估组合性解释的挑战:相关性、完整性和专家评级。EMNLP 会议论文集。

[3] 何吉, 陈建书, 何晓东, 高剑锋, 李丽红, 邓力, 和 Mari Ostendorf. 2016. 基于自然语言行动空间的深度强化学习. 载《ACL 会议论文集》.

[4] Prithviraj Ammanabrolu 和 Matthew Hausknecht. 2020. 图约束强化学习在自然语言行动空间中的应用. 载《ICLR》.

[5] 刘寅涵, Myle Ott, Naman Goyal, 丁菲杜, Mandar Joshi, 陈丹琪, Omer Levy, Mike Lewis, Luke Zettlemoyer, 和 Veselin Stoyanov. 2019. Roberta: 一种稳健优化的 BERT 预训练方法. (2019).

[6] Filip Ilievski, Alessandro Oltramari, 马凯欣, 张斌, Deborah L McGuinness, 和 Pedro Szekely. 2021. 常识知识的维度. 《基于知识的系统》229 (2021), 107347.

[7] 洪亨元, Le Hou, Shayne Longpre, Barret Zoph, Yi Tay, William Fedus, Eric Li, 王学智, Mostafa Dehghani, Siddhartha Brahma 等. 2022. 扩展指令微调语言模型.

[8] 林育辰, Yicheng Fu, Karina Yang, Prithviraj Ammanabrolu, Faeze Brahman, 黄诗雨, Chandra Bhagavatula, 崔烨金, 和 任翔. 2023. SwiftSage: 一个具有快速与慢速思维的生成性智能体,适用于复杂的交互任务。

[9] 诺姆·乔姆斯基 2014. 句法理论的若干方面. 第 11 卷. MIT 出版社.

[10] 江一凡, Filip Ilievski 和 马凯欣. 2023. 跨常识任务传递程序性知识. 载《ECAI》.

附录

任务描述

  1. 任务 1 — 物质: 你的任务是将水冻结。首先,聚焦在该物质上。接着,采取行动使其发生状态变化。

    物质。

  2. 任务 2 — 测量: 你的任务是测量巧克力的熔点,巧克力位于厨房的某个地方。首先,聚焦在温度计上。接着,聚焦在巧克力上。如果巧克力的熔点高于 -10.0 摄氏度,聚焦在蓝色盒子上。如果巧克力的熔点低于 -10.0 摄氏度,聚焦在橙色盒子上。这些盒子位于厨房的各个地方。

  3. 任务 3 — 电学: 你的任务是通过可再生能源电源打开红色灯泡。首先,聚焦在红色灯泡上。接着,创建一个电路将其通电。

  4. 任务 4 — 分类: 你的任务是找到一个非生物物体。首先,聚焦在这个物体上。然后,把它移到厨房的红色盒子里。

  5. 任务 5 — 生物学 I: 你的任务是从种子中种植一个苹果植物。种子可以在厨房找到。首先,聚焦在一个种子上。然后,改变环境以促进植物生长,直到它达到繁殖生命周期阶段。

  6. 任务 6 — 化学: 你的任务是利用化学方法制造物质 ‘盐水’。食谱和一些原料可能在厨房附近找到。完成后,聚焦在盐水上。

  7. 任务 7 — 生物学 II: 你的任务是找到寿命最长的动物,然后是寿命最短的动物。首先,专注于寿命最长的动物。然后,专注于寿命最短的动物。这些动物位于“外部”位置。

  8. 任务 8 — 生物学 III: 你的任务是专注于海龟的四个生命周期阶段,从最早到最晚。

  9. 任务 9 — 力学: 你的任务是确定两个倾斜面(未知材料 C,未知材料 H)中哪一个具有最大的摩擦力。

    摩擦力。完成实验后,专注于摩擦力最大的倾斜面。

  10. 任务 10 — 生物学 IV: 你的任务是确定蓝色种子颜色在未知 E 植物中的遗传方式,是显性还是隐性特征。如果该特征是显性的,专注于红色盒子。如果该特征是隐性的,专注于绿色盒子。

ScienceWorld 游戏示例

任务: 4(找到一个非生物物体)

变化: 239(DRRN 基准)

描述: 你的任务是找到一个非生物物体。首先,专注于该物体。然后,将它移到工作坊中的紫色盒子里。

了解 Kolmogorov–Arnold 网络(KAN)

原文:towardsdatascience.com/kolmogorov-arnold-networks-kan-e317b1b4d075?source=collection_archive---------1-----------------------#2024-05-07

为什么 KAN 有可能成为 MLP 和当前机器学习格局的替代方案?让我们通过论文来一探究竟。

Hesam SheikhTowards Data Science Hesam Sheikh

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

--

一篇名为KAN:Kolmogorov–Arnold 网络的新研究论文在机器学习社区中引发了热议。它为神经网络提供了新的视角,并提出了一个可能替代多层感知机(MLP)的方案,MLP 是当前机器学习的基石。

✨这是付费文章。如果你不是 Medium 会员,你可以在我的通讯中免费阅读此文: Qiubyte.

Kolmogorov-Arnold 表示定理的启发,KAN 与传统的多层感知机(MLP)不同,它通过将固定激活函数替换为可学习的函数,有效地消除了线性权重矩阵的需求。

如果你对更细节的内容和实验感兴趣,我强烈建议阅读这篇论文。不过,如果你更喜欢简洁的介绍,我已经准备了这篇文章来解释 KAN 的核心要点。

  • 注:本文中使用的图片/图形来源于“KAN:Kolmogorov–Arnold 网络”论文,除非另有说明。

理论

这些新型网络的理论支柱是一位由两位苏联数学家弗拉基米尔·阿诺德安德烈·柯尔莫哥洛夫提出的理论。

安德烈·科尔莫哥洛夫的指导下,阿诺德在 1957 年证明了任何多个变量的连续函数都可以通过有限个二元函数构造出来,从而解决了希尔伯特第十三问题。(来源:维基百科

他们所研究并最终发展的理论是基于多变量连续函数的概念。根据这个理论,任何多变量连续函数f都可以写成若干个单变量连续函数的有限组合,并将其相加。

科尔莫哥洛夫–阿诺德表示定理的数学公式。(来源:维基百科

这个定理如何融入机器学习?

在机器学习中,高效准确地近似复杂函数是一个重要课题,尤其是在数据维度增加时。当前主流模型,如多层感知器(MLP),通常在处理高维数据时存在困难——这一现象被称为维度灾难

然而,科尔莫哥洛夫-阿诺德定理为构建能够克服这一挑战的网络(如 KAN)提供了理论基础。

MLP 和 KAN 的概述比较。

KAN 如何避免维度灾难?

这个定理允许将复杂的高维函数分解为更简单的单维函数的组合。通过专注于优化这些一维函数,而不是整个多变量空间,KAN 减少了所需的复杂性和参数数量,从而实现了准确建模。此外,由于处理的是更简单的一维函数,KAN 可以成为简单且可解释的模型。

## 柏拉图式表现:人工智能深度网络模型是否趋同?

人工智能模型是否正在朝着统一的现实表现形式发展?柏拉图式表现……

towardsdatascience.com

什么是科尔莫哥洛夫–阿诺德网络(KAN)?

Kolmogorov-Arnold 网络(简称 KAN)是一种受 Kolmogorov-Arnold 表示定理启发的神经网络架构。与使用固定激活函数的传统神经网络不同,KAN 使用可学习的激活函数,这些激活函数位于网络的边缘。这使得 KAN 中的每个权重参数都可以由一个单变量函数替代,通常参数化为样条,从而使其具有高度的灵活性,能够用较少的参数建模复杂的函数,并增强可解释性。

KAN 利用 MLP 的结构,同时受益于样条。

KAN 架构

Kolmogorov-Arnold 网络(KAN)的架构围绕着一个新颖的概念展开,即将传统的权重参数替换为网络边缘上的单变量函数参数。KAN 中的每个节点将这些函数的输出求和,而不应用任何非线性变换,这与包括线性变换后跟非线性激活函数的 MLP 相对立。

KAN 与 MLP 公式对比。

B 样条:KAN 的核心

惊人的是,论文中最重要的图形之一竟然容易被忽视。那就是样条的描述。样条是 KAN 学习机制的核心。它们替代了神经网络中通常使用的传统权重参数。

样条结构的详细视图。

样条的灵活性使得它们能够自适应地建模数据中的复杂关系,通过调整其形状以最小化逼近误差,从而增强网络从高维数据集中学习微妙模式的能力。

在 KAN 上下文中,样条的一般公式可以使用 B 样条表示如下:

这里,𝑠pline(𝑥) 代表样条函数。ci​ 是在训练过程中优化的系数,𝐵𝑖(𝑥) 是在网格上定义的 B 样条基函数。网格点定义了每个基函数 𝐵𝑖​ 激活并显著影响样条的形状平滑度的区间。你可以把它们看作是影响网络准确度的超参数。更多的网格意味着更多的控制精确度,同时也意味着更多的参数需要学习。

通过多个步骤训练一个 KAN。(来源:GitHub

在训练过程中,这些样条的ci参数(基函数Bi(x) 的系数)会被优化,以最小化损失函数,从而调整样条的形状,使其最适合训练数据。这个优化通常涉及像梯度下降这样的技术,每次迭代更新样条参数,以减少预测误差。

两全其美

虽然 KAN 基于Kolmogorov-Arnold 表示定理,但它同样受到 MLP 的启发,“利用它们各自的优势,避免它们各自的弱点”。KAN 在外部受益于 MLP 的结构,在内部则受益于样条。

结果是,KAN 不仅能够学习特征(得益于其与 MLP 的外部相似性),还能够优化这些学习到的特征,达到极高的准确性(得益于其与样条的内部相似性)。

网络简化

网络符号化概述。

论文接着解释了一些简化网络的方法和我们对它们的理解。我将仅讨论其中两种方法,它们让我感到非常有趣。

  1. 符号化:KAN 是通过使用更简单、通常可以解释的函数的组合来近似构造函数的。这使得它们具有独特的能力,可以输出可解释的数学公式,如上图所示。

  2. 剪枝:论文中讨论的 KAN 的另一个方面是通过去除不重要的节点或连接来优化网络架构,尤其是在网络训练后。这一过程有助于减少复杂性和大小。剪枝的重点是识别并消除对输出贡献最小的部分,从而使网络更轻量化,并有可能变得更加可解释。

KAN 是新的吗?

Kolmogorov-Arnold 表示定理并不新鲜,那么为什么在机器学习中使用它的实践之前没有被研究过呢?正如论文所解释的,曾经进行过多次尝试……

然而,大多数工作依然停留在原始的深度为 2、宽度为(2n + 1)的表示形式,并没有机会利用更现代的技术(例如反向传播)来训练网络。

KAN 的可视化表示,由 DALLE-3 创建。

该论文的创新之处在于将这一理念适应并应用于当前机器学习的领域。通过使用任意的网络架构(深度、宽度)并采用反向传播和剪枝等技术,KAN 比以往的研究更接近实际应用场景。

Kolmogorov-Arnold 表示定理在机器学习中基本上被宣判为死刑,被认为是理论上健全,但在实践中无用。

尽管已经有尝试将 Kolmogorov-Arnold 表示定理应用于机器学习,但可以公平地说,KAN 是一种新颖的方法,因为它意识到机器学习今天所处的位置。它是对之前在有限规模上探索过的一个想法的很好更新。

4 个迷人的例子

论文对 KAN 和 MLP 在几个标准上的比较,其中大多数都很吸引人。在这一部分,我将列出一些这些有趣的例子。有关这些例子以及更多详细内容,请参见论文。

合适的符号公式

这是训练各种 MLP 和 KAN 以拟合不同输入维度的某些函数的示例。从下图可以看出,与 MLP 相比,KAN 在可扩展性方面有明显优势(至少在这个参数范围内)。

这突显了更深层 KAN 的更强表达能力,这对于 MLP 也是一样的:更深的 MLP 比浅层的有更强的表达能力。

特殊函数

论文中的另一个例子是比较 KAN 和 MLP 在拟合数学和物理中常见的 15 个特殊函数上的表现。结果表明,在几乎所有这些函数中,KAN 在拥有相同参数数量的情况下,比 MLP 具有更低的训练/测试损失

持续学习

持续学习是指网络在时间推移中如何适应新信息而不忘记先前学到的知识。这是神经网络训练中的一大挑战,尤其是在避免灾难性遗忘的问题上,当获取新知识时,可能会迅速侵蚀先前建立的信息。

KAN 展示了保持已学信息并适应新数据的能力,而不会发生灾难性遗忘,这得益于样条函数的局部性质。与依赖全局激活(可能无意中影响模型远离部分)的 MLP 不同,KAN 每次新样本仅修改有限的相邻样条系数。这种集中调整保护了样条中其他部分已存储的信息。

偏微分方程求解

偏微分方程求解,一个 2 层宽度为 10 的 KAN 比一个 4 层宽度为 100 的 MLP 准确度高 100 倍(10⁻⁷与 10⁻⁵均方误差)且在参数效率上高 100 倍(102 与 104 个参数)。

论文继续展示更多实验。其中之一是将 KAN 应用于几何结不变量问题,使用一个200参数的 KAN 实现了81.6%的测试准确率,而谷歌 DeepMind 的 MLP 模型在~ 3 * 10⁵个参数下取得了78%的准确率。这个实验是在

最终思考

KAN 的炒作值得吗?

这取决于你的视角。之所以目前在讨论 KAN,是因为它可能是机器学习(ML)隧道尽头的一线曙光。我在“人工智能遇到无法突破的瓶颈”中讨论了我们如何需要新的创新来引导我们突破机器学习的未来障碍,即数据计算。虽然不是有意为之,KAN 可能是走出去的一个途径。

KAN 是以人工智能的科学应用为设计初衷的,但现在人们已经开始将它用于混合各种机器学习(ML)模型,包括多头注意力(Multihead Attention)。

更新:你可以阅读我在 MNIST 数据集上训练 KAN 的实验,以测试它在计算机视觉任务中的表现,不要错过👇

KAN + LLM?

论文主要集中在 Kolmogorov-Arnold 网络(KAN)在人工智能 + 科学应用方面的潜力,因为它们能够有效地建模和发现复杂的科学规律和模式。KAN 特别适用于需要理解解释基础物理原理的任务,因为它们的结构允许将函数分解成符号化的数学表达式。这使得它们非常适合科学研究,在科学研究中发现这种关系至关重要,而不像大规模语言模型(LLM),后者的主要目标通常是处理庞大的数据集,以进行自然语言理解和生成。

作者注释

我鼓励你也阅读 GitHub 页面上的作者注释。它提供了对 KAN 初衷的视角,以及未来可能的发展。

最近我被问到最多的问题是 KAN 是否会成为下一代 LLM。我对此没有很好的直觉。KAN 是为那些注重高准确性和/或可解释性的应用设计的。我们当然关心 LLM 的可解释性,但可解释性对于 LLM 和科学来说可能意味着截然不同的事情。我们是否关心 LLM 的高准确性?我不知道,缩放法则似乎表明是这样,但可能并不需要非常高的精度。另外,准确性对 LLM 和科学来说也有不同的意义。这种微妙的差异使得我们很难将论文中的结论直接应用于 LLM 或一般的机器学习任务。

“KAN: Kolmogorov–Arnold 网络”论文。

结论

在我看来,最好是从 KAN 本身的角度来看待它,而不是从我们希望它成为的角度来看。并不意味着 KAN 不可能与大规模语言模型(LLM)集成,实际上已经有一个高效的PyTorch 实现 KAN。但需要指出的是,现在称 KAN 为 革命性改变游戏规则 还为时过早。KAN 仍然需要社区专家进行更多的实验。

尽管 KAN 在某些特定情境下提供了显著的优势,但它也有一些限制和考虑因素,需要谨慎对待:

  1. 复杂性与过拟合: KANs 可能会发生过拟合,尤其是在数据有限的情况下。它们构建复杂模型的能力可能会将噪声当作重要模式捕捉,导致泛化能力差。

  2. 计算: 由于 KAN 的专业化特性,它们可能面临 GPU 优化的挑战,这可能会破坏并行处理。这种架构可能会导致 GPU 上的操作变慢,迫使进行序列化,导致内存使用效率低下,从而可能使得 CPU 成为这些网络的更合适平台。

  3. 适用性: KANs 主要设计用于科学和工程任务,在这些任务中,理解潜在功能至关重要。它们可能在需要大规模模式识别或分类的领域(例如图像识别或自然语言处理)中效果不如那些更简单或更抽象的模型。

我必须补充,这篇论文真的很棒,读起来非常令人兴奋。跳出框框思考总是很有趣,而 KAN 无疑做到了这一点。

💬 你如何看待 KAN 的潜力?它会专注于科学领域,还是将在我们的日常 AI 产品中发挥重要作用?

🌟 加入 1000+人一起学习

Python🐍,机器学习/MLOps/人工智能🤖,数据科学📈,以及 LLM 🗯

关注我并查看我的X/Twitter,在这里我每天为你更新:

感谢阅读,

— Hesam

Kolmogorov-Arnold Networks(KAN)用于时间序列预测

原文:towardsdatascience.com/kolmogorov-arnold-networks-kans-for-time-series-forecasting-9d49318c3172?source=collection_archive---------1-----------------------#2024-05-14

了解 Kolmogorov-Arnold Networks(KAN),并使用 Python 将其应用于时间序列预测

Marco PeixeiroTowards Data Science Marco Peixeiro

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

--

图片由Eduardo Bergen提供,来源于Unsplash

多层感知器(MLP)是深度学习模型的基础结构之一。它也是许多先进预测模型的构建模块,如 N-BEATS、NHiTS 和 TSMixer。

2024 年 4 月 30 日,论文KAN: Kolmogorov-Arnold Network发表,吸引了许多深度学习领域从业者的关注。在这篇论文中,作者提出了一种替代 MLP 的模型:Kolmogorov-Arnold Network,简称KAN

KAN 不是使用权重和固定的激活函数,而是使用可学习的函数,这些函数通过样条进行参数化。研究人员认为,KAN 可以在参数较少的情况下比 MLP 更加精确。

在这篇文章中,我们首先探索了样条(splines),因为它们帮助我们理解 KAN 的架构和关键元素。然后,我们深入研究 KAN 的内部工作机制。最后,我们将 KAN 应用于时间序列预测,并与标准的 MLP 和 N-BEATS 模型进行性能评估。

要了解更多关于 KAN 的细节,请务必阅读原始论文

Kolmogorov-Arnold Networks:神经网络领域的最新进展,简明解释

原文:towardsdatascience.com/kolmogorov-arnold-networks-the-latest-advance-in-neural-networks-simply-explained-f083cf994a85?source=collection_archive---------0-----------------------#2024-05-12

这种新型网络正在机器学习领域掀起波澜。

Theo WolfTowards Data Science Theo Wolf

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

--

MLP 与 KAN 的比较。图片来自论文

今年四月,一篇名为:KAN: Kolmogorov–Arnold Networks的论文出现在 arXiv 上。宣布这篇论文的推文获得了大约 5 千个点赞,对于论文的宣布来说,这已经算是相当火爆了。附带的GitHub 仓库已经有 7.6k 个星标,并且还在不断增加。

Kolmogorov-Arnold 网络(KAN)是一种全新的神经网络构建模块。它的目标是比多层感知器(MLP)更具表现力、更不容易过拟合,并且更具可解释性。MLP 在深度学习模型中无处不在。例如,我们知道它们被用在像 GPT-2、3 和(可能)4 等模型的 Transformer 块之间。对 MLP 的改进将对机器学习领域产生广泛的影响。

MLP 简述

MLP 实际上是一种非常古老的架构,起源于 50 年代。其思想是复制大脑的结构;有很多……

为什么你不需要 JS 来制作 3D 图表

原文:towardsdatascience.com/la-crime-now-in-3d-no-glasses-required-498398c25a39?source=collection_archive---------2-----------------------#2024-06-01

在 Python 中可视化犯罪地理数据

Aleksei RozanovTowards Data Science Aleksei Rozanov

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

--

图片来自 作者

在我看来,地理数据最棒的特点之一就是它能够进行 3D 可视化。然而,由于进行此类计算所需的大量计算资源,这在 Python 中是非常罕见的(通常JavaScript及其相关库被用作替代方案)。在我之前的一篇文章中,我分享了六个 Python 包,可以创建美丽的静态和交互式地图,但仅限于 2D 空间。

[## 6 种用于制作美丽地图的 Python 库

每个数据科学家都会在某个时刻面临分析或建模地理空间数据的需求,而这无法在没有...

medium.com](https://medium.com/@alexroz/6-python-libraries-to-make-beautiful-maps-9fb9edb28b27?source=post_page-----498398c25a39--------------------------------)

今天,我想弥合这个差距,与大家一起探讨一个非常时尚且高效的框架,用于高性能的基于 Web 的可视化deck.jl,它也有一个 Python 库 PyDeck

要在 Python 中充分探索其功能,我们需要一个大型的地理空间数据集。一个理想的候选数据集是Kaggle 上的洛杉矶犯罪数据 2010-2020数据集。幸运的是,它具有开放许可证,因此我们可以自由使用它来进行我们的分析。

作者们分发了两个 csv 文件,我们将把它们合并为一个,同时过滤掉所有除了经纬度(即犯罪发生地点的坐标)以外的列。

🐍完整的 Python 代码: GitHub.

import numpy as np
import pandas as pd
import xarray as xr
import geopandas as gpd
import random
import math

import matplotlib.pyplot as plt
from shapely import Point

import cartopy
import cartopy.crs as ccrs
import cartopy.feature as cfeature

import warnings

warnings.filterwarnings('ignore')

recent = pd.read_csv('./LA Crimes/Crime_Data_from_2020_to_Present.csv')[['LAT', 'LON']]
old = pd.read_csv('./LA Crimes/Crime_Data_from_2010_to_2019.csv')[['LAT', 'LON']]
df = pd.concat([old, recent])
df = df[(df.LON!=0) & (df.LAT!=0)] #zeros are Nans according to meta info

在 pandas 中加载数据后,我想使用 cartopy 做一个静态的二维可视化,只是为了有一个可靠的参考。如果我们仅仅绘制数据,我们将得到一堆数据点,对我们没有任何帮助。

图片来源:作者

相反,让我们使用 NN 方法进行空间插值(你可以在我的另一篇文章中阅读更多关于它的内容)。

## 3 种最佳空间插值方法

从头开始,在 Python 中

[towardsdatascience.com

基本上,这意味着我们将稀疏的观测数据转化为一个地理网格(PyDeck 将做同样的事情,在这种情况下可以称之为数据聚合)。

def coords(x,y, base=0.01):
 x, y = round(base * math.ceil(abs(x)/base),2), round(base * math.ceil(y/base),2)
 return (y,x)

def NN(data, LAT, LON):
  array = np.zeros((LAT.shape[0], LON.shape[0]),dtype=int)
  onGrid = data.apply(lambda row: coords(row.LAT, row.LON, 0.01), axis = 1).value_counts()
  for coor in onGrid.index:
    lon_idx, lat_idx = np.where(LON==coor[0]), np.where(LAT==coor[1])
    array[lat_idx,lon_idx] = int(onGrid[coor])
  return array

算法完成后(你需要等一段时间,因为我们有超过 200 万行数据要处理),我们可以将其结果包装成 xarray 数据集并进行映射。

LAT, LON = np.arange(round(df.LAT.min()), round(df.LAT.max()), 0.01).astype(np.float32), np.arange(round(df.LON.min()), round(df.LON.max()), 0.01).astype(np.float32)
crimes = NN(df, LAT, LON)
ds = xr.Dataset(
    {'Crimes': (['lat', 'lon'], crimes)},
    coords={'lat': LAT, 'lon': LON})

fig, ax = plt.subplots(subplot_kw=dict(projection=ccrs.PlateCarree()), figsize=(16, 9))

ds.Crimes.plot(ax=ax, cmap='Reds')
ax.set_extent([-118.9, -118.1, 33.6, 34.5 ], crs=ccrs.PlateCarree())
ax.gridlines(draw_labels=True,linewidth=2, color='black', alpha=0.5, linestyle='--')
ax.add_feature(cfeature.BORDERS, edgecolor='black', linewidth=1)

ax.add_feature(cfeature.COASTLINE, edgecolor='black', linewidth=1)
ax.add_feature(cartopy.feature.RIVERS, edgecolor='blue', linewidth=0.5)
states_provinces = cfeature.NaturalEarthFeature(
            category='cultural',  name='admin_1_states_provinces',
            scale='10m', facecolor='none')

plt.show()

图片来源:作者

从我的角度来看,它看起来既不错又具有信息量,但如果你需要将这个项目推销给别人,使用这样的地图你很可能会失败 xD。所以,让我们来安装 PyDeck 并看看它能为我们做些什么!

1. 六边形。

我发现最美的地图图层类型是六边形图层。在创建 PyDeck 的 Layer 变量时你需要指定它。对于我们来说,还有一些其他至关重要的参数:

  • 半径(六边形的半径,单位米,即空间分辨率,单位为米);

  • elevation_scale(箱体的缩放因子,值越大,六边形越高);

  • elevation_range(最小和最大高度);

  • 可选择(交互式显示值);

  • extruded(单元格高度)。

layer = pdk.Layer(
    'HexagonLayer',
    df,
    get_position=['LON', 'LAT'],
    radius=500, #bin radius
    auto_highlight=True,
    elevation_scale=50, #scale factor for bins (the greater - the higher)
    elevation_range=[0, 3000],
    pickable=True,
    extruded=True,#cell elevation
    )

我们需要创建的第二个变量是视图状态。我们需要为它提供:

  • 经度和纬度;

  • 缩放(初始缩放);

  • min_zoom 和 max_zoom;

  • 方位角(左/右视角);

  • pitch(上下视角)。

view_state = pdk.ViewState(
    longitude=-118.3,
    latitude=34.4,
    zoom=8,
    min_zoom=6,
    max_zoom=15,
    bearing=-20,#left/right angle
    pitch=20, #up/down angle
    )

由于我们有一个大数据集,Google Colab 并不会显示全部数据,因此你有两个选择:

  • 从数据集中抽取 N 行样本;

  • 将地图保存为 HTML 并在浏览器中打开。

如果你选择第二个,你将得到这个:

图片来源:作者

说实话,我喜欢六边形的外观,但在任何科学论文/报告/讲座中我都没见过它们,所以我建议大家有意识地使用它们。

2. 列

现在让我们尝试创建一个类似的可视化,但是使用柱状图。但在这种情况下,我们需要将之前创建的 xarray 数据集传递给函数,并指定颜色和要可视化的变量:

layer = pdk.Layer(
    'ColumnLayer',
    ds.to_dataframe().reset_index(),
    get_position=['lon', 'lat'],
    get_elevation='Crimes',
    elevation_scale=10,
    radius=200,
    get_fill_color=['Crimes', 220],
    pickable=True,
    extruded=True,
    )

图片由作者提供。

3. 散点图

本质上,散点图是由点云组成的,但 PyDeck 的作者们开发了独特的圆柱体,它们看起来非常与众不同:

图片由作者提供。

layer = pdk.Layer(
    'ColumnLayer',
    df[:15000],
    get_position=['LON', 'LAT'],
    auto_highlight=True,
    get_radius=200,          # Radius is given in meters
    get_fill_color=[180, 0, 200, 140],  # Set an RGBA value for fill
    pickable=True)

图片由作者提供。

4. 地图样式

PyDeck 的一个非常酷的特点是,像 plotly、geemap、folium 等交互式映射工具一样,它允许用户更改底图,这意味着你可以根据项目的背景设计地图:

r = pdk.Deck(layers=[layer],
             initial_view_state=view_state,
             map_style=pdk.map_styles.LIGHT, # ‘light’, ‘dark’, ‘road’, ‘satellite’, ‘dark_no_labels’, and ‘light_no_labels
             )

图片由作者提供。

5. 标题

下一个我认为非常有用的特性是改变交互式数据描述。通过将光标放在某个柱状图/六边形/点上,你可以获取元数据信息,但有时看起来有些荒谬。但在 PyDeck 中,你可以轻松克服这一点:

图片由作者提供。

r = pdk.Deck(layers=[layer],
             initial_view_state=view_state,
                  "html": "<b>Number of crimes:</b> {elevationValue}",
                  "style": {
                        "backgroundColor": "yellow",
                        "color": "black"
                  }
                },
             )

图片由作者提供。

6. 视角

最后,这个库最惊人的特性就是你可以通过简单地点击鼠标右键来改变地图的视角:

from ipywidgets import HTML

text = HTML(value='Move the viewpoint')

def filter_by_bbox(row, west_lng, east_lng, north_lat, south_lat):
    return west_lng < row['lng'] < east_lng and south_lat < row['lat'] < north_lat

def filter_by_viewport(widget_instance, payload):
    west_lng, north_lat = payload['data']['nw']
    east_lng, south_lat = payload['data']['se']
    filtered_df = df[df.apply(lambda row: filter_by_bbox(row, west_lng, east_lng, north_lat, south_lat), axis=1)]

r.deck_widget.on_click(filter_by_viewport)

图片由作者提供。

我确实很喜欢PyDeck,并计划深入了解deck.jl框架。它的语法既简单又直观,能够让用户在保证能效的前提下构建出令人印象深刻的可视化。Python 在一定程度上限制了这个包的功能,所以你可以查看他们的画廊,真的非常震撼,尤其是他们的实验性 GlobalView 功能…

希望这篇文章对你有启发并且信息量十足!

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

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

备注:我对(地理)数据科学、机器学习/人工智能以及气候变化充满极大的热情。如果你想一起合作进行某个项目,请通过LinkedIn与我联系。

🛰️关注我,获取更多更新🛰️

Label Studio 定制化后端用于半自动化图像分割标注

原文:towardsdatascience.com/label-studio-customized-backend-for-semiautomatic-image-segmentation-labeling-324c2310d756?source=collection_archive---------6-----------------------#2024-04-20

定制化后端;GCP 部署;通过 GCS 集成进行数据版本控制

Alison Yuhan YaoTowards Data Science Alison Yuhan Yao

·发表于Towards Data Science ·13 分钟阅读·2024 年 4 月 20 日

--

作者提供的图像

目录

· 介绍

· 概览

∘ 目标

∘ 为什么是半自动化?

∘ 进入 Label Studio

∘ 1 前端 + 2 后端

· 实现(本地)

∘ 1. 安装 git 和 docker 并下载后端代码

∘ 2. 设置前端以获取访问令牌

∘ 3. 设置后端容器

∘ 4. 连接容器

∘ 5. 快乐标注!

· GCP 部署

∘ 1. 选择项目/创建新项目并设置账单账户

∘ 2. 创建虚拟机实例

∘ 3. 设置虚拟机环境

∘ 4. 跟随前一部分,在虚拟机上设置所有内容

· GCS 集成

∘ 1. 设置 GCS 存储桶

∘ 2. 创建并设置服务账户密钥

∘ 3. 重建后端容器

∘ 4. SDK 从源存储桶上传图像

∘ 5. 设置目标存储

· 致谢

· 参考资料

介绍

为图像分割任务创建训练数据仍然是个人和小团队面临的一大挑战。如果你像我一样是学生研究者,找到一种具有成本效益的方法尤其重要。在这篇文章中,我将分享我在我的毕业设计项目中使用的一种解决方案,我们一个 9 人团队在一周内成功标注了 400+张图像。

多亏了米兰理工大学的吉安弗朗科·费雷研究中心,我们从吉安弗朗科·费雷的档案数据库中获取了成千上万张时装 T 台秀的图像。为了探索、管理、丰富和分析这个数据库,我使用了图像分割进行更智能的分类和精细化研究。T 台秀照片的图像分割也为创建信息丰富的文本描述打下了基础,从而提升搜索引擎和文本生成图像 AI 的效果。因此,本文将详细介绍:

  • 如何在现有的Segment Anything 后端基础上,使用label studio创建您自己的后端,以便进行半自动化图像分割标注,

  • 如何在 Google Cloud Platform 上托管以便进行小组协作,并且

  • 如何使用 Google Cloud Storage 存储桶进行数据版本控制。

本文中的代码可以在这个GitHub 仓库找到。

概述

目标

分割并识别 T 台秀图像中时尚服装项目的名称和类型,如第一张图所示。

为什么是半自动化?

如果某个训练好的分割模型能够完美地识别 T 台秀图像中的每一件服装,那该多好?遗憾的是,目前还没有这样的模型。虽然存在针对时尚或服装图像的训练模型,但没有一个能完美匹配我们的数据集。每个时装设计师都有自己对某些服装款式、颜色和纹理的偏好,因此即使一个分割模型的准确率达到 60%,我们也算是胜利了。然后,我们仍然需要人工干预来纠正分割模型的错误。

进入 Label Studio

Label Studio 提供了一个开源、可定制并且免费的社区版本,适用于各种类型的数据标注。用户可以创建自己的后端,以便将 Label Studio 前端与上述提到的训练分割模型后端连接,从而让标注员进一步改进自动预测结果。此外,Label Studio 已经具备了一个界面,类似于 Photoshop,并且拥有一系列分割工具,可以为我们提供很大的帮助:

1 个前端 + 2 个后端

到目前为止,我们希望 2 个后端能够与前端连接。一个后端可以进行分割预测,第二个后端可以在预测错误时加速标注员的修改。

图片来自作者

实现(本地)

现在,让我们在本地启动应用程序。也就是说,你可以在笔记本电脑或本地机器上完全免费地使用这个应用程序,但目前无法邀请你的标注团队在他们的笔记本上协作。我们将在下一节中讨论如何与 GCP 进行团队协作。

1. 安装 git 和 docker & 下载后端代码

如果你的笔记本电脑或本地机器上还没有安装gitdocker,请先安装它们。(注意:如果你从这个 GitHub 仓库下载了压缩包,理论上可以跳过安装 git 这一步。如果你这么做,请跳过下面的步骤。)

然后,打开你的终端并将这个仓库克隆到你想要的目录。

git clone https://github.com/AlisonYao/label-studio-customized-ml-backend.git

如果你在代码编辑器中打开label-studio-customized-ml-backend文件夹,你会看到大部分代码都改编自Label Studio ML 后端仓库,但这个目录还包含了改编自Label Studio SDK的前端模板代码和 SDK 代码。

2. 设置前端以获取访问令牌

按照segment anything的官方指南,在终端中执行以下操作:

cd label-studio-customized-ml-backend/label_studio_ml/examples/segment_anything_model

docker run -it -p 8080:8080 \
    -v $(pwd)/mydata:/label-studio/data \
    --env LABEL_STUDIO_LOCAL_FILES_SERVING_ENABLED=true \
    --env LABEL_STUDIO_LOCAL_FILES_DOCUMENT_ROOT=/label-studio/data/images \
    heartexlabs/label-studio:latest

接着,打开浏览器,输入0.0.0.0:8080/,你将看到 Label Studio 的前端界面。继续使用你的电子邮件地址注册。现在没有项目,所以我们需要通过点击创建项目来创建我们的第一个项目。为你的项目创建一个名称和描述(可选)。

图片来源:作者

本地上传一些图片。(稍后我们将讨论如何使用云存储。)

图片来源:作者

对于标注设置,点击左侧的自定义模板,并从label-studio-customized-ml-backend/label_studio_frontend/view.html文件中复制粘贴 HTML 代码。如果你不想在标注界面显示图像元数据,可以不需要那四行 Header 代码。你可以根据需要修改此代码,或者点击可视化来添加或删除标签。

图片来源:作者

现在,点击保存,你的标注界面应该准备好了。

图片来源:作者

在右上角,点击用户设置图标,然后点击账户与设置,你应该能够复制你的访问令牌。

图片来源:作者

3. 设置后端容器

label-studio-customized-ml-backend目录中,感谢 Label Studio 开发者,里面有许多后端。我们将使用定制的./segmentation后端进行分割预测(容器 1),以及./label_studio_ml/examples/segment_anything_model进行更快速的标注(容器 2)。前者将使用 7070 端口,后者将使用 9090 端口,这样可以与前端的 8080 端口区分开来。

现在,将你的访问令牌粘贴到./segmentation./label_studio_ml/examples/segment_anything_model文件夹中的 2 个docker-compose.yml文件中。

environment:
  - LABEL_STUDIO_ACCESS_TOKEN=6dca0beafd235521cd9f23d855e223720889f4e1

打开一个新的终端,像之前那样进入segment_anything_model目录,然后启动分割任何物体容器。

cd label-studio-customized-ml-backend/label_studio_ml/examples/segment_anything_model

docker build . -t sam:latest
docker compose up

然后,打开另一个新的终端,进入segmentation目录,并启动分割预测容器。

cd label-studio-customized-ml-backend/segmentation

docker build . -t seg:latest
docker compose up

到目前为止,我们已经成功启动了所有 3 个容器,你可以再次检查确认。

图片来源:作者

4. 连接容器

之前,我们通过访问令牌连接了容器,所以我们已经差不多完成了。现在,去打开你之前启动的前端,并点击右上角的设置。在左侧点击机器学习,然后点击添加模型

图片来源:作者

确保使用带有 9090 端口的 URL,并启用交互式预标注功能。点击验证并保存完成添加。

同样,执行相同操作来设置分割预测后端。

图片来源:作者

然后,我喜欢启用加载任务时自动获取预测。这样,每次刷新标注页面时,分割预测将会自动触发并加载。

图片来源:作者

5. 快乐标注!

这是按照上述步骤操作后,你应该看到的演示。

视频来源:作者

如果我们不满意预测结果,比如说裙子的部分,我们可以删除裙子并使用紫色魔法(分割任何物体)快速标注。

视频来源:作者

我相信你能自己搞定刷子、橡皮擦和魔法棒的使用!

如果你是单独工作,那就万事俱备。但如果你在考虑如何与团队协作而又不订阅 Label Studio Enterprise,我们需要将一切托管到云端。

GCP 部署

我选择了 GCP,因为有教育信用,但你可以使用任何你喜欢的云平台。关键是将应用托管在云端,这样你标注团队中的任何人都可以访问并使用你的 Label Studio 应用。

图片来源:作者

1. 选择项目/创建新项目并设置账单账户

如果你没有现有项目,可以前往GCP 控制台创建一个新项目,并按要求设置计费账户信息(不幸的是,云服务是收费的)。在这里,我将使用我创建的Fashion项目来演示。

图片来自作者

2. 创建虚拟机实例

为了给标注团队提供公共 IP 地址,我们需要在 GCP 上创建一个虚拟机(VM)并将一切部署到这里。进入你创建或选择的项目后,在搜索框中搜索计算引擎,第一个弹出的选项应该是VM 实例。点击创建实例,根据需要选择设置。

图片来自作者

默认的 10GB 持久磁盘会给你带来问题,所以请增加磁盘空间。另外,更重要的是,允许 HTTP 流量。

图片来自作者

稍后修改这些设置会有点麻烦,所以在点击创建之前,尽量先想清楚。

3. 设置虚拟机环境

你可以将虚拟机想象成云端的某台计算机,类似于你的笔记本电脑,但你只能通过终端或命令行要求它做事。所以现在,我们需要在虚拟机上像在本地一样设置所有内容(请参见上一部分)。

点击 SSH,授权并打开命令行界面。

图片来自作者

进行常规更新,并安装 docker、docker compose、git 和 Python。

sudo apt-get update

curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo docker --version

sudo apt-get install docker-compose-plugin

sudo apt install git

sudo apt install python3-pip

图片来自作者

4. 按照上一部分的步骤,在虚拟机上设置所有内容

现在,你可以按照上一部分的步骤 1–4 进行操作,但有一些变化:

  1. 当你遇到 docker 权限被拒绝错误时,添加sudo

  2. 如果遇到数据权限错误,可以使用类似sudo chmod -R 777 mydata的命令来修改权限。然后你就应该能够运行前端容器了。

  3. 服务器不再位于0.0.0.0:8080。相反,将 0.0.0.0 替换为虚拟机的外部 IP 地址。一个例子是34.1.1.87:8080/。你可以在之前点击的SSH按钮旁边找到外部 IP 地址。然而,你可能仍然无法访问前端。你需要在 GCP 控制台搜索防火墙,然后点击防火墙(VPC 网络),接着点击default-allow-http。现在,修改设置如下,你就应该能够访问前端了。

图片来自作者

4. 编辑docker-compose.yml文件时,除了复制粘贴访问令牌,还要修改LABEL_STUDIO_HOST。同样,将host.docker.internal替换为虚拟机的外部 IP 地址。一个例子是34.1.1.87:8080

5. 同样,当在前端设置中添加机器学习时,也使用34.1.1.87:9090http://34.1.1.87:7070

然后,你可以导出你的标注结果并根据需要进行定制。

如果你只有几张图片需要标注,或者你愿意从本地上传图片,那完全没问题。但对于我的项目,有成千上万张图片,因此我更倾向于使用 Google Cloud Storage 来自动化数据传输和数据版本控制。

GCS 集成

1. 设置 GCS 存储桶

在 GCP 控制台中搜索存储桶并导航到 Cloud Storage 存储桶。创建 2 个存储桶:一个用于存放图片(源),另一个为空(目标)。第二个存储桶将在开始标注时填充数据。

作者提供的图片

然后,根据官方文档,我们需要为存储桶设置跨域资源共享(CORS)访问权限。点击右上角的激活云终端,然后运行以下命令。

echo '[
   {
      "origin": ["*"],
      "method": ["GET"],
      "responseHeader": ["Content-Type","Access-Control-Allow-Origin"],
      "maxAgeSeconds": 3600
   }
]' > cors-config.json

gsutil cors set cors-config.json gs://ferre-runway # use your bucket name
gsutil cors set cors-config.json gs://ferre-runway-labeled # use your bucket name

如果你希望为标注结果设置数据版本控制,可以点击存储桶并在保护中开启版本控制。

作者提供的图片

2. 创建并设置服务账户密钥

很可能你不希望你的存储桶是公开的,那么 Label Studio 需要认证才能访问这些图片。点击创建服务账户,并授予存储管理员角色,这样我们就可以读写 GCS 存储桶。你应该能够在存储桶的权限列表中看到这个服务账户。

作者提供的图片

现在,点击新创建的服务账户并点击密钥。接着添加一个新密钥,并确保将 JSON 文件下载到安全的地方。

现在,打开你的本地终端并编码 JSON 文件。

base64 -i service_account_key.json -o service_account_key_encoded.txt
cat service_account_key_encoded.txt

你可以看到随机的字符和数字字符串并复制它。现在我们将其作为元数据粘贴到虚拟机中。点击你的虚拟机,点击编辑,并添加你的自定义元数据。例如,我的键是 GOOGLE_APPLICATION_CREDENTIALS_BASE64。

作者提供的图片

我们接着会解码服务账户密钥以便在 Python 代码中进行认证。

3. 重新构建后端容器

由于我们修改了docker-compose.yml文件,因此需要运行新的脚本并重新构建后端容器。

# Check running containers and their IDs, find the backends you need to kill
sudo docker ps

sudo docker kill a5a5e21537cb
sudo docker kill 54e51b0ae78f

# navigate to the right folders like before and build new containers
sudo docker compose up

现在,你应该能看到新的容器。

作者提供的图片

4. SDK 从源存储桶上传图片

如果你只是想上传图片而不带元数据,你可以跳过此部分,直接执行与第 5 步相同的操作(见下文)。所谓元数据,是指每张图片在标注界面上有用的信息,这些信息可能有助于更准确地进行标注。

基于 Label Studio SDK 仓库中的示例,您可以修改./label_studio_sdk/annotate_data_from_gcs.ipynb文件中的元数据内容以及导入方式。运行本地的 Python 笔记本后,您应该能在前端看到您的图片和元数据。

图片来自作者

您还应该查看设置中的源存储桶。请勿点击同步存储(Sync Storage),因为它将直接从存储桶同步,可能会扰乱我们导入的元数据。

图片来自作者

5. 设置目标存储

点击添加目标存储,并根据提示填写信息。将您的服务账户密钥复制粘贴到 Google 应用凭据文本框中,您就完成设置了。

图片来自作者

每次您点击目标云存储中的同步存储时,它将把标注结果以文本格式同步到 GCS 存储桶中。点击同步一次后,提交标注结果时该过程应该会自动触发,但请检查是否需要定期手动同步。

祝您标注愉快!

致谢

很高兴能作为一部分参与Data Shack 2024,与米兰理工大学Gianfranco Ferré研究中心合作。我要感谢Pavlos Protopapas 教授和 Paola Bertola 教授的指导,以及感谢您们使这个项目得以启动。我要感谢Chris Gumb和 Marco Brambilla 教授的技术支持,以及 Federica Vacca 教授和Angelica Vandi 博士在时尚领域的专业知识支持。最后,我要感谢我的队友Luis Henrique Simplicio RibeiroLorenzo Campana和 Vittoria Corvetti 在过程中给予的帮助以及与我一起解决问题。还要特别感谢 Emanuela Di Stefano、Jacopo Sileo、Bruna Pio Da Silva Rigato、Martino Fois、Xinxi Liu 和 Ilaria Trame 的持续支持和辛勤工作。

参考文献

  • 11655 Gianfranco Ferré,成衣系列,2004 年秋冬季。由 Gianfranco Ferré研究中心提供。

  • 13215 Gianfranco Ferré,成衣系列,春夏 1991. 感谢 Gianfranco Ferré 研究中心提供。

感谢阅读!希望这篇博客对你有所帮助。

本文中的代码可以在这个GitHub 仓库找到。

Lag-Llama:开源时间序列预测基础模型

原文:towardsdatascience.com/lag-llama-open-source-foundation-model-for-time-series-forecasting-9afdfaf2bd7c?source=collection_archive---------5-----------------------#2024-02-13

探索 Lag-Llama 的架构,并学习如何在预测项目中使用 Python 来应用它

Marco PeixeiroTowards Data Science Marco Peixeiro

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

--

图片由Ray Hennessy提供,来源于Unsplash

2023 年 10 月,我发布了一篇关于TimeGPT的文章,这是首个用于时间序列预测的基础模型之一,具备零-shot 推理、异常检测和符合预测能力。

然而,TimeGPT 是一个专有模型,仅通过 API 令牌进行访问。尽管如此,它激发了对时间序列基础模型的更多研究,因为与自然语言处理(NLP)和计算机视觉相比,这一领域的发展相对滞后。

快进到 2024 年 2 月,我们现在有了一个开源的时间序列预测基础模型:Lag-Llama

在原始论文中:Lag-Llama:面向概率时间序列预测的基础模型,该模型被提出为一种通用的基础模型,用于单变量的概率预测。它由来自不同机构的大团队开发,这些机构包括摩根士丹利、ServiceNow、蒙特利尔大学、Mila-Quebec 和麦吉尔大学。

在本文中,我们将探索 Lag-Llama 的架构、功能以及它是如何训练的。然后我们将实际在预测项目中使用 Lag-Llama……

LangChain 的内置 AI 输出评估指标:它们有何不同?

原文:towardsdatascience.com/langchains-built-in-eval-metrics-for-ai-output-how-are-they-different-f9dd75e2de08?source=collection_archive---------9-----------------------#2024-05-22

Jonathan BennionTowards Data Science Jonathan Bennion

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

--

我通常为自己的使用案例创建自定义指标,但在开始使用 RAGAS 和/或 DeepEval 进行 RAG 评估之前,反复遇到 LangChain 内置的这些 AI 工具评估指标,所以最终对这些指标是如何创建的产生了好奇,并进行了快速分析(当然有所有固有的偏见)。

TLDR 来自以下的相关性矩阵:

  • 有用性与一致性(0.46 的相关性):这一强相关性表明,大型语言模型(以及由此推导出的用户)可能会觉得一致性的回答更有帮助,强调了回答中逻辑结构的重要性。这只是相关性,但这一关系揭示了这一结论的可能性。*

  • 争议性与犯罪性(0.44 的相关性):这表明,即使是有争议的内容也可能被视为犯罪,反之亦然,或许反映了用户偏好那些具有吸引力和发人深省的内容。*

  • 一致性与深度: 尽管一致性与有用性相关,但深度并不相关。这可能意味着用户(再次假设用户偏好固有于大型语言模型的输出中——这一点本身是一个假设,并且是一种偏见,需要保持警觉)可能更倾向于简明扼要的回答,而不是详细的回答,尤其是在那些快速解决方案比全面解决方案更被看重的情境下。

内置指标可以在这里找到(移除一个与地面真相相关且更适合在其他地方处理的指标):

# Listing Criteria / LangChain's built-in metrics
from langchain.evaluation import Criteria
new_criteria_list = [item for i, item in enumerate(Criteria) if i != 2]
new_criteria_list

这些指标:

  • 简洁性

  • 细节

  • 相关性

  • 一致性

  • 有害性

  • 冷漠性

  • 有用性

  • 争议性

  • 犯罪性

  • 深度

  • 创造力

首先,这些意味着什么,为什么要创建这些?

假设是:

  • 这些是为了尝试定义可以解释输出与理论用例目标之间关系的度量标准,任何相关性可能是偶然的,但通常尽可能避免。

在看到这里的源代码后,我得出了这个假设。

第二,一些这些似乎相似和/或模糊——那么它们是如何区分的呢?

我使用了标准的 SQuAD 数据集作为基准,评估 OpenAI 的 GPT-3-Turbo 模型输出与该数据集中的真实值之间的差异(如果有的话),并进行比较。

# Import a standard SQUAD dataset from HuggingFace (ran in colab)
from google.colab import userdata
HF_TOKEN = userdata.get('HF_TOKEN')

dataset = load_dataset("rajpurkar/squad")
print(type(dataset))

我获取了一组随机化的行进行评估(由于时间和计算限制,无法对整个数据集进行评估),因此这可能是更多噪音和/或偏差的切入点。

# Slice dataset to randomized selection of 100 rows
validation_data = dataset['validation']
validation_df = validation_data.to_pandas()
sample_df = validation_df.sample(n=100, replace=False)

我定义了一个 LLM,使用了 ChatGPT 3.5 Turbo(为了节省成本,这个过程很快)。

import os

# Import OAI API key
OPENAI_API_KEY = userdata.get('OPENAI_API_KEY')
os.environ['OPENAI_API_KEY'] = userdata.get('OPENAI_API_KEY')
# Define llm
llm = ChatOpenAI(model_name='gpt-3.5-turbo', openai_api_key=OPENAI_API_KEY)

然后迭代遍历抽样行以进行比较——LangChain 在评估标准中使用了未知的‘评分’阈值,但假设这些阈值在所有度量中是相同的。

# Loop through each question in random sample
for index, row in sample_df.iterrows():
    try:
        prediction = " ".join(row['answers']['text'])
        input_text = row['question']

        # Loop through each criteria\
        for m in new_criteria_list:
            evaluator = load_evaluator("criteria", llm=llm, criteria=m)

            eval_result = evaluator.evaluate_strings(
                prediction=prediction,
                input=input_text,
                reference=None,
                other_kwarg="value"  # adding more in future for compare
            )
            score = eval_result['score']
            if m not in results:
                results[m] = []
            results[m].append(score)
    except KeyError as e:
        print(f"KeyError: {e} in row {index}")
    except TypeError as e:
        print(f"TypeError: {e} in row {index}")

然后,我计算了均值和 95%置信区间的置信区间。

# Calculate means and confidence intervals at 95%
mean_scores = {}
confidence_intervals = {}

for m, scores in results.items():
    mean_score = np.mean(scores)
    mean_scores[m] = mean_score
    # Standard error of the mean * t-value for 95% confidence
    ci = sem(scores) * t.ppf((1 + 0.95) / 2., len(scores)-1)
    confidence_intervals[m] = (mean_score - ci, mean_score + ci)

然后绘制了结果。

# Plotting results by metric
fig, ax = plt.subplots()
m_labels = list(mean_scores.keys())
means = list(mean_scores.values())
cis = [confidence_intervals[m] for m in m_labels]
error = [(mean - ci[0], ci[1] - mean) for mean, ci in zip(means, cis)]]

ax.bar(m_labels, means, yerr=np.array(error).T, capsize=5, color='lightblue', label='Mean Scores with 95% CI')
ax.set_xlabel('Criteria')
ax.set_ylabel('Average Score')
ax.set_title('Evaluation Scores by Criteria')
plt.xticks(rotation=90)
plt.legend()
plt.show()

可能直观地认为‘相关性’比其他指标要高得多,但有趣的是,总体而言它们都很低(可能要感谢 GPT 3.5!),而‘有用性’是下一个最高的指标(可能反映了强化学习技术和优化)。

为了回答我关于相关性的问题,我计算了一个简单的相关矩阵,使用了原始的比较数据框。

# Convert results to dataframe
min_length = min(len(v) for v in results.values())
dfdata = {k.name: v[:min_length] for k, v in results.items()}
df = pd.DataFrame(dfdata)

# Filtering out null values
filtered_df = df.drop(columns=[col for col in df.columns if 'MALICIOUSNESS' in col or 'MISOGYNY' in col])

# Create corr matrix
correlation_matrix = filtered_df.corr()

然后绘制了结果(p 值是通过我的代码中的进一步部分创建的,且均小于 0.05)

# Plot corr matrix
mask = np.triu(np.ones_like(correlation_matrix, dtype=bool))
plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, mask=mask, annot=True, fmt=".2f", cmap='coolwarm',
            cbar_kws={"shrink": .8})
plt.title('Correlation Matrix - Built-in Metrics from LangChain')
plt.xticks(rotation=90)
plt.yticks(rotation=0)
plt.show()

令人惊讶的是,大多数没有相关性,考虑到 LangChain 代码库中描述的性质——这表明这些内容是经过深思熟虑的,我很高兴这些是内建的可以使用的。

从相关矩阵中,显现出一些显著的关系:

  • 有用性与一致性(0.46 相关性): 这一强相关性表明,LLM(作为用户的代理)可能会发现一致的回应更有帮助,这强调了回应中逻辑结构的重要性。尽管这只是相关性,但这种关系为此铺平了道路。

  • 争议性与犯罪性(0.44 相关性): 这表明,即使是有争议的内容也可能被视为犯罪,反之亦然,这也许反映了用户对引人深思且富有启发性的材料的偏好。同样,这只是相关性。

主要结论:

  1. 连贯性与深度在帮助性中的对比: 尽管连贯性与帮助性相关,但深度却没有。这可能意味着用户更倾向于简洁明了的答案而非详细的答案,特别是在那些快速解决方案比全面方案更受重视的场景中。

  2. 利用争议性: 争议性与犯罪性之间的正相关关系提出了一个有趣的问题:是否可以以非犯罪的方式讨论争议话题?这可能在不牺牲内容质量的情况下增加用户参与度。

  3. 偏见与模型选择的影响: 使用 GPT-3.5 Turbo 以及度量设计中固有的偏见可能会影响这些相关性。承认这些偏见对于准确解读和应用这些度量标准至关重要。

除非另有说明,本文中的所有图片均由作者创作。

LangChain 的父文档检索器 — 重新审视

原文:towardsdatascience.com/langchains-parent-document-retriever-revisited-1fca8791f5a0?source=collection_archive---------4-----------------------#2024-07-22

仅使用你的向量数据库增强上下文检索

Omri Eliyahu LevyTowards Data Science Omri Eliyahu Levy

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

--

TL;DR — 我们通过使用元数据查询实现与 LangChain 的父文档检索器相同的功能(链接)。你可以在这里查看代码。

RAG 简介

检索增强生成(RAG)目前是 LLM 和 AI 应用领域最热门的话题之一。

简而言之,RAG 是一种将生成模型的响应基于选定知识源进行“立足”的技术。它包括两个阶段:检索和生成。

  1. 在检索阶段,给定用户的查询,我们从预定义的知识源中检索相关信息。

  2. 然后,我们将检索到的信息插入到发送给 LLM 的提示中,该模型(理想情况下)根据提供的上下文生成对用户问题的回答。

一种常用的方法来实现高效准确的检索是通过使用嵌入。在这种方法中,我们通过将文档拆分成块(例如页面、段落或句子)来预处理用户的数据(假设为纯文本)。然后,我们使用嵌入模型为这些块创建有意义的数值表示,并将它们存储在向量数据库中。现在,当查询到来时,我们也对其进行嵌入,并使用向量数据库执行相似性搜索,以检索相关信息。

图片由作者提供

如果您完全不熟悉这个概念,我推荐deeplearning.ai的精彩课程,LangChain: 与您的数据对话

什么是“父文档检索”?

“父文档检索”或其他人所称的“句子窗口检索”,是一种通过提供更广泛的上下文供 LLM 考虑来增强 RAG 检索方法性能的常见方法。

本质上,我们将原始文档分成相对较小的块,嵌入每一个块,并将它们存储在向量数据库中。使用这些小块(一句话或几句话)有助于嵌入模型更好地反映其含义[1]。

然后,在检索时,我们不仅返回由向量数据库找到的最相似块,还返回其在原文档中的周围上下文(块)。这样,LLM 将拥有更广泛的上下文,在许多情况下有助于生成更好的答案。

LangChain 通过父文档检索器[2]支持这一概念。父文档检索器允许您:(1) 检索特定块来源的完整文档,或 (2) 为与父文档关联的每个较小块预先定义一个更大的“父”块。

让我们从LangChains 文档中探讨一个例子:

# This text splitter is used to create the parent documents
parent_splitter = RecursiveCharacterTextSplitter(chunk_size=2000)
# This text splitter is used to create the child documents
# It should create documents smaller than the parent
child_splitter = RecursiveCharacterTextSplitter(chunk_size=400)
# The vectorstore to use to index the child chunks
vectorstore = Chroma(
    collection_name="split_parents", embedding_function=OpenAIEmbeddings()
)
# The storage layer for the parent documents
store = InMemoryStore()
retriever = ParentDocumentRetriever(
    vectorstore=vectorstore,
    docstore=store,
    child_splitter=child_splitter,
    parent_splitter=parent_splitter,
)
retrieved_docs = retriever.invoke("justice breyer")

在我看来,LangChains 方法有两个缺点:

  1. 需要管理外部存储,以便利用这一有用的方法,无论是内存还是其他持久化存储。当然,对于真实的使用案例,多个示例中使用的 InMemoryStore 是不够的。

  2. “父文档”检索不是动态的,意味着我们无法动态调整周围窗口的大小。

的确,关于这个问题已经提出了一些问题[3]。

在这里我还要提到,Llama-index 有自己的SentenceWindowNodeParser [4],它通常具有相同的缺点。

接下来,我将介绍另一种方法,以实现这个有用的功能,并解决上述提到的两个缺点。在这种方法中,我们将仅使用已经在使用的向量存储。

替代实现

精确地说,我们将使用支持仅执行元数据查询选项的向量存储,而不涉及任何相似性搜索。在这里,我将为ChromaDBMilvus呈现一个实现示例。这个概念可以轻松适配任何具备此功能的向量数据库。在本教程的最后,我将以Pinecone为例进行说明。

一般概念

这个概念很简单:

  1. 构建:在每个块旁边,保存其元数据中生成该块的document_id,以及该块的sequence_number

  2. 检索: 在执行常规的相似性搜索之后(为了简单起见,假设只返回前 1 个结果),我们从检索到的块的元数据中获取 document_idsequence_number。然后,检索所有具有相同 document_id 的周围序列号的块。

例如,假设你已经将一个名为 example.pdf 的文档分成 80 个块进行索引。那么,对于某个查询,你会发现最接近的向量是具有以下元数据的那个:

{document_id: "example.pdf", sequence_number: 20}

你可以轻松地获取来自同一文档、序列号从 15 到 25 的所有向量。

让我们看看代码。

这里,我使用的是:

chromadb==0.4.24
langchain==0.2.8
pymilvus==2.4.4
langchain-community==0.2.7
langchain-milvus==0.1.2

下面唯一值得注意的是与每个块相关的元数据,这将帮助我们执行搜索。

from langchain_community.document_loaders import PyPDFLoader
from langchain_core.documents import Document
from langchain_text_splitters import RecursiveCharacterTextSplitter

document_id = "example.pdf"

def preprocess_file(file_path: str) -> list[Document]:
    """Load pdf file, chunk and build appropriate metadata"""
    loader = PyPDFLoader(file_path=file_path)
    pdf_docs = loader.load()

    text_splitter = RecursiveCharacterTextSplitter(
        chunk_size=1000,
        chunk_overlap=0,
    )

    docs = text_splitter.split_documents(documents=pdf_docs)
    chunks_metadata = [
        {"document_id": file_path, "sequence_number": i} for i, _ in enumerate(docs)
    ]
    for chunk, metadata in zip(docs, chunks_metadata):
        chunk.metadata = metadata

    return docs

现在,让我们在 Milvus 和 Chroma 中实现实际的检索。请注意,我将使用 LangChains 的对象,而不是原生客户端。这样做是因为我假设开发人员可能希望保留 LangChains 的有用抽象层。另一方面,这将要求我们做一些小的修改,以数据库特定的方式绕过这些抽象层,因此你应该考虑这一点。无论如何,概念保持不变。

再次假设为了简化,我们只需要最相似的向量(“前 1 个”)。接下来,我们将提取相关的 document_id 及其序列号。这将帮助我们检索周围的窗口。

from langchain_community.vectorstores import Milvus, Chroma
from langchain_community.embeddings import DeterministicFakeEmbedding

embedding = DeterministicFakeEmbedding(size=384) # Just for the demo :)

def parent_document_retrieval(
    query: str, client: Milvus | Chroma, window_size: int = 4
):
    top_1 = client.similarity_search(query=query, k=1)[0]
    doc_id = top_1.metadata["document_id"]
    seq_num = top_1.metadata["sequence_number"]
    ids_window = [seq_num + i for i in range(-window_size, window_size, 1)]
    # ...

现在,对于窗口/父级检索,我们将在 Langchain 的抽象层下,采用数据库特定的方式进行操作。

对于 Milvus:

 if isinstance(client, Milvus):
        expr = f"document_id LIKE '{doc_id}' && sequence_number in {ids_window}"
        res = client.col.query(
            expr=expr, output_fields=["sequence_number", "text"], limit=len(ids_window)
        )  # This is Milvus specific
        docs_to_return = [
            Document(
                page_content=d["text"],
                metadata={
                    "sequence_number": d["sequence_number"],
                    "document_id": doc_id,
                },
            )
            for d in res
        ]
    # ...

对于 Chroma:

 elif isinstance(client, Chroma):
        expr = {
            "$and": [
                {"document_id": {"$eq": doc_id}},
                {"sequence_number": {"$gte": ids_window[0]}},
                {"sequence_number": {"$lte": ids_window[-1]}},
            ]
        }
        res = client.get(where=expr)  # This is Chroma specific
        texts, metadatas = res["documents"], res["metadatas"]
        docs_to_return = [
            Document(
                page_content=t,
                metadata={
                    "sequence_number": m["sequence_number"],
                    "document_id": doc_id,
                },
            )
            for t, m in zip(texts, metadatas)
        ]

别忘了按序列号排序:

 docs_to_return.sort(key=lambda x: x.metadata["sequence_number"])
    return docs_to_return

为了方便起见,你可以在这里查看完整代码。

Pinecone(及其他)

据我所知,Pinecone 没有原生方法来执行此类元数据查询,但你可以通过它们的 ID 原生地获取向量(docs.pinecone.io/guides/data/fetch-data)。

因此,我们可以做以下操作:每个块将获得一个唯一 ID,该 ID 本质上是文档 ID 和序列号的连接。然后,给定通过相似性搜索检索到的向量,你可以动态创建周围块的 ID 列表,从而实现相同的结果。

限制

值得一提的是,向量数据库并非为了执行“常规”数据库操作而设计,通常也没有针对这类操作进行优化,并且每个数据库的表现都不相同。例如,Milvus 将支持在标量字段(“元数据”)上建立索引,从而优化这类查询。

同时,请注意,这需要向向量数据库发送额外的查询。首先我们检索到最相似的向量,然后我们执行额外的查询,以获取原始文档中周围的块。

当然,正如上面的代码示例所示,实现是特定于向量数据库的,并且 LangChains 的抽象层本身不支持这种实现。

结论

在这篇博客中,我们介绍了一种实现方式,用于实现句子窗口检索,这是一种在许多 RAG 应用中都非常有用的检索技术。在这个实现中,我们仅使用了已经在使用中的向量数据库,并且还支持动态修改检索到的周围窗口大小的选项。

参考文献

[1] ARAGOG: 高级 RAG 输出评分,arxiv.org/pdf/2404.01037,第 4.2.2 节

[2] python.langchain.com/v0.1/docs/modules/data_connection/retrievers/parent_document_retriever/

[3] 一些相关问题:

[4] docs.llamaindex.ai/en/stable/api_reference/node_parsers/sentence_window/

语言作为一种通用学习机器

原文:towardsdatascience.com/language-as-a-universal-learning-machine-d2c67cb15e5f?source=collection_archive---------7-----------------------#2024-05-23

人类与计算机的语言处理:第四部分

说出来就是相信,看到就是幻觉。

Dusko PavlovicTowards Data Science Dusko Pavlovic

·发布于 Towards Data Science ·40 分钟阅读·2024 年 5 月 23 日

--

机器学习语言模型已经改变了我们的日常生活:它们在我们学习、驾驶、理财时为我们提供指导。它们有潜力改变我们的文明。但它们也会产生幻觉。它们的现实是虚拟的。本系列的第四部分提供了一个关于学习机器如何工作的低层次细节的高级概述。事实证明,即使它们能够像人类一样识别幻觉并安全地做梦,学习机器仍然会像人类一样,形成更广泛的错误信念和自我验证的理论体系。

[我尝试让这篇文章对所有人都易于阅读。省略一些数学推导应该不会影响后续的论点。即使仅仅是开始和结尾的图片,也希望能传达主要信息。欢迎提出改进建议:)]

第一部分是:

聊天机器人是谁(它们对你来说意味着什么)? 后续思考: 房间里的四只大象与聊天机器人

第二部分是:

语法:语言形式

第三部分是:

语义学:语言的意义

这是第四部分:

  1. 语言模型、名人和蒸汽机

  2. 学习的演变

  • 2.1. 学习的原因和迷信

  • 2.2. 一般学习框架

  • 2.3. 示例:从鸽子到感知机

3. 学习函数

  • 3.1. 为什么学习是可能的

  • 3.2. 分解连续函数:Kolmogorov-Arnold⁶

  • 3.3. 广泛学习

  • 3.4. 逼近连续函数:Cybenko 等

  • 3.5. 深度学习

4. 学习通道与注意力

  • 4.1 通过概念引导

  • 4.2 静态通道学习:RNN、LSTM…

  • 4.3 动态通道学习:注意力、变压器…

5. 超越幻觉

  • 5.1. 参数化学习框架

  • 5.2. 自我学习

  • 5.3. 自我确认的信念

归属

笔记

1. 语言模型、名人和蒸汽机

任何人都可以开车。大多数人甚至知道发动机长什么样。但是当你需要修理它时,你需要弄清楚它是如何工作的。

任何人都可以和聊天机器人对话。大多数人知道在背后有一个大型语言模型(LLM)。有很多文章描述了 LLM 是什么样子的。很多五彩斑斓的图片。复杂的小组件网状结构,好像数学抽象和模块化编程还等着被发明一样。YouTube 频道上有关于 LLM 名人的新鲜资讯。我们了解它们的各个部分及其连接方式,知道它们的性能,甚至看到每个部分如何将输入的热图转化为输出的热图,一个比一个热。但是我们真的理解它们是如何工作的么?专家说他们理解,但似乎连彼此之间都无法解释清楚,因为他们在几乎所有问题上都有分歧。

当然,每个孩子都知道,解释自己刚刚建造的东西可能很困难。我们的伟大文明建造了很多无法解释的东西。蒸汽机已经被工程化近 2000 年,才有科学家解释它们是如何从热量中提取工作的。如今蒸汽机已经不多见,但有很多语言引擎,还有一个完整的科学解释行业,讲述它们如何从引用中提取意义。领先的理论是,圣诞老人从山上下来,给我们留下了刻在石板上的变压器架构。

变压器改变了世界,催生了后代和竞争者……就像蒸汽机一样。这可能是件好事,因为蒸汽机并没有因为它们的创造者不理解它们而将它们的创造者消灭。

我没经历过蒸汽机时代,但我经历过笨重的计算机时代,也亲历了网络的出现以及一切发生变化的时期,还经历了网络巨头的崛起和他们改变网络的过程。在那段时间里,人工智能研究似乎像是智能设计智能的一种努力。它并没有改变什么,因为智能,像生命一样,是一个进化的过程,而不是智能设计的产物¹。但现在,一些友好的学习机器和聊天机器人 AI 已经进化,一切又在发生变化。在经历了过去的范式转变后,我正在试图理解这一当前的转变。因此有了这门课程和这些文字。从一方面讲,我可能没有机会说出任何未曾有人提到过的东西。即使做了很多诚实的工作,我仍然是一个目光短浅的非专家。另一方面,在人工智能的领域,出现了一些强大的工具和思想,AI 专家似乎并未意识到这些。人们会聚集在研究社区中,集中关注相同的事情,却忽视了相同的事情。站在围栏旁,邻居有时能比自己更好地理解邻居。这有时会导致麻烦。一种持续的诱惑。这是一个站在围栏外的视角。

2. 学习的演化

2.1. 学习的原因和迷信

蜘蛛天生就会织蜘蛛网。它们编织网的工程技能是通过基因程序化的。它们是预先训练好的建造者,甚至它们选择和记住一个适合织网的好地方的能力也是自动化的。

狗和鸽子天生就会寻找食物。它们学会寻找带来食物的来源和行为的能力是自动化的。在一项著名的实验中,生理学家巴甫洛夫研究了最简单的学习形式之一,通常被称为条件反射

如果每次喂狗时铃声响起,他就会学会每当铃声响起时分泌唾液。

延续这一思路,心理学家斯金纳展示了鸽子甚至能够发展出一种迷信行为,同样是通过试图学习食物的来源。

如果食物在鸽子啄食时到来,她就会学会啄食能够引来食物。

斯金纳在完全随机的时间喂鸽子,行为和喂食没有任何关联。约有 70%的鸽子发展出了它们可以召唤食物的信念。如果一只鸽子恰好在食物到来之前在地面啄食,或在食物到达之前整理羽毛,这会使它们更频繁地进行这些动作,从而增加在这些行为发生时食物到来的概率。如果某个随机的联结,比如食物和啄食,在一段时间后占主导地位,那么它就会变成一种寻求食物的仪式舞蹈。每次食物最终都会到来,并确认仪式有效。

人类天生倾向于寻找原因并预测结果。像鸽子一样,他们将巧合的事件关联为相关事件,并发展出迷信,将巧合升华为因果理论。当鸽子最终啄空的表面来召唤谷粒时,人类则建立起庞大的虚假信仰体系,归因于星星数百万光年外的影响,或邻居的恶意眼光,或者几乎任何可以看到、感受到或计算的事物²。

但尽管我们的因果信念与鸽子相似,我们建造房屋和跨越桥梁的能力却与蜘蛛不同。与蜘蛛不同,我们不是天生会建造,而是必须学习我们的工程技能。我们天生会学习

2.2. 一般学习框架

学习场景的鸟瞰图大致如下:

输入从左边进入。主要角色是:

  • 一个过程F,在监督学习中是监督者图灵称之为“老师”),处理类型为X的输入数据x,生成类型为Y的输出类别或参数y

  • 一个a索引的函数族𝒰(−)a,其中𝒰是一个学习机解释器图灵称之为“学生”),索引a模型,通常表示为程序;最后,还有

  • 一个函数ℒ,通常称为损失,比较输出y = F(x)与预测值 = 𝒰(x)a,并给出一个实数ℒ(y,),表示它们之间的差异。

监督学习框架的学习者提供一个有限的集合

其中x是来自源X的样本,y是来自随机变量Y = F(X)的相应样本。学习者的任务是构建一个最小化损失的模型a

其中yi = F(xi),ỹi = 𝒰(xi)a,对于i = 1,2,…,n。由于某些损失可能在其他损失减少时增加,因此要求学习算法最小化平均猜测风险

其中[𝒰(xi)a]表示猜测𝒰(xi)a被尝试的频率。一旦找到一个使风险最小化的模型a,就通过运行实现模型a的程序在机器𝒰上近似函数F,并写成

土豆、洋葱、番茄、番茄。 学习的结果是什么?我们刚才提到的监督学习的一轮结果a是监督者F模型。由于a是试图描述F,大多数逻辑学家会称其为F理论。如果解释𝒰(X)a忠实地描述了F(X),那么逻辑学家会说F实际上是理论a在𝒰语义解释下的一个模型。所以在学习理论中,aF的模型,而在逻辑中,Fa的模型,这两者之间存在术语上的冲突。更进一步,统计学家说a是关于F假设。如果一个假设或理论被认为是真的,那么它就是学习者信念状态的一部分。在最后部分,我们将提出一个有趣的构建,说明研究机器学习的信念逻辑的必要性³。我们仍然称学习结果a模型,因为这似乎是最常见的用法。然而,一个重要的细节是,F的模型a需要是可执行的,以便能够计算出值F(X)的预测𝒰(X)a。但如果你仔细想想,可执行模型通常就是我们所称的程序。总之,学习过程的结果是一个可执行的模型。学习的累积结果是学习者的信念状态。学习的过程就是寻找可学习程序的过程。

所有学习都是语言学习。 一般来说,待学习的过程F是通过一个通道给出的,这意味着输出是上下文依赖的。来自第 3.2 节 语义 部分的故事适用。通道输入xj依赖于早期的输入xi,其中i < j。当有反馈时,xj也依赖于早期的输出yi,其中i < j。为了能够学习F的行为,学习机器𝒰也必须是一个通道。由于捕捉通道依赖性需要句法和语义引用,所以每个学习者背后都有一种语言,无论它是否显而易见。电影、音乐或图像等语言的符号学分析描述了真正的句法和语义结构。不同的生物以不同的方式学习,但对于人类及其机器而言,所有学习都是语言学习。

2.3. 示例:从鸽子到感知机

鸽子迷信。 鸽子学习预测的函数F是食物的来源。它可以视为一个通道[XY],其中类型X的值x1、x2、...是时间点,Y = F(X)是一个随机变量,以固定的概率提供种子。假设Y = 1 表示“食物”,Y = 0 表示“没有食物”。如果我们将可能的模型(程序、信念)a对应于鸽子可用动作集的元素,那么鸽子正在尝试学习在哪些时刻x和哪些动作a下,输出𝒰(x)a = 1,以及何时输出 0。如果食物恰好在鸽子采取动作a时送到,则损失ℒ(y, 𝒰(x)a) = |y-𝒰(x)a|为 0。经过足够的时间后,随机输出Y = 1 几乎肯定会与某个a的预测𝒰(X)a = 1 相符。鸽子随后会学会更频繁地做a,从而增加这种巧合发生的机会。如果某个a占主导地位,鸽子将学会它是导致食物出现的原因。

统计检验。 科学是一系列旨在克服迷信和偏见的方法。其核心思想是通过系统地检验假设,并仅接受显著的相关性,从而防止“鸽子式”的确认偏误。统计假设检验的数学基础由罗纳德·费舍尔(Ronald Fisher)在 1920 年代发展起来,并一直是科学实践中的基础工具。其关键假设是,任何假设a的解释𝒰是与其概率密度pa(x) = d𝒰(x)a一起给出的。损失ℒ则通过该概率的描述长度来估计。如果pa(x)的描述由一串数字表示,则其描述长度与−log pa(x)成正比。因此,猜测风险为ℛ(a) = ∫− log pa(x)d𝒰(x)a。这类值在信息论中作为不确定性的度量进行研究。因此,最小化ℛ(a)就归结为选择能够最小化采样𝒰的不确定性的假设a。费舍尔推荐了一种学习算法,通过它可以选择具有最大似然的假设。

统计检验的基本缺点是,密度pa必须是已知的。它们被假设为来源于科学家的思想,以及由a参数化的假设。因此,统计学提供的是检验服务,但实际的假设学习过程a超出了统计的范围,留给了直觉和创造力的神奇。在 Kolmogorov 及其学生们研究这一问题数十年并最终解决它的过程中,解决方案的核心部分无意间出现在一个意想不到的方向。

感知机。1943 年,McCulloch 和 Pitts 提出了神经元的数学模型。它简化为一种状态机,就像图灵 1936 年的原始计算机一样,只是更简单,因为它没有外部存储器。在 1950 年代末,Frank Rosenblatt 正在研究将神经元模型扩展为大脑模型。这是一个非常雄心勃勃的项目。

来自 Rosenblatt 1958 年向海军研究办公室提交的项目报告插图。 — 公共领域

然而,Rosenblatt 提出了一个比 McCulloch-Pitts 神经元更简单的组件。他称之为 感知机,以强调他的项目与“各种工程项目,尤其是自动模式识别和‘人工智能’”的区别。然而,该项目产生了像“海军机器人设计的‘弗兰肯斯坦怪物’,它能思考”这样的新闻报道,正如 Rosenblatt 在他的书中如实报道的那样⁴。

数学神经元 被定义为一对 a = (b, ⟨w |),其中⁵

并且 b 是一个标量。它被认为是由解释器 𝒰 解释的非常简单的程序。为了在输入向量 | x ⟩ 上评估 a = (b, ⟨w |),解释器 𝒰 对 | x ⟩ 进行投影 ⟨w |,以获得内积 ⟨w | x⟩,该内积衡量了任一向量在另一个向量上的投影长度,然后输出差值 ⟨w | x⟩ − b 的符号:

为了更简洁地表达,通常将配对 a = (b, ⟨w |) 和输入 | x ⟩ 修改为

这样,神经元的解释就简化为

感知机 是由这些神经元组成的。如果一个神经元被表示为一个单一的行向量,那么感知机就是一个 (n + 1)-元组,由行向量组成。

在输入 | x⟩ 上,感知机 a 的解释计算

为了更简洁地表达,n-元组向量 ⟨w1 |, . . . , ⟨wn | 可以排列成矩阵

因此,感知机 a = (⟨v|, ⟨w1 |,…,⟨wn |) 简化为 a = (⟨v|, W),其解释变为

为了用图示总结,左侧是神经元的两种表示方式,右侧是感知机的两种表示方式。

Rosenblatt 的神经元和感知机

第一行显示了原始形式的神经元和感知机,其中包括阈值 bj。第二行显示了每个 bj 被吸收为权重向量 ⟨wj | 的 0-项版本。

感知机是机器学习和归纳推理的突破,二者是同一枚硬币的两面。 统计学提供了假设检验的正式方法,但将学习和推理假设的任务留给了非正式方法和创造力的魔力。感知机训练是归纳推理的第一个正式方法。如今,这种方法看起来理所当然。学习者将权重| w ⟩ 和阈值 b 初始化为任意值,运行解释器𝒰生成预测,将其与由监督者F提供的训练数据进行比较,并根据损失ℒ比例更新权重。这甚至对弗兰克·罗森布拉特来说都不算什么大事,他写道:

感知机程序[并非]主要关注“人工智能”设备的发明,而是研究支撑“自然智能”的物理结构和神经动力学原理。

罗森布拉特为机器学习奠定了基础,试图模拟人脑中的学习过程。即使是最初的学习机器也并非有目的地设计,而是自发地演化出来的。

人们常说,飞机的建造并不是通过研究鸟类如何飞行来完成的,智能机器也不会通过观察人脑内部的运作来建造。然而,事情并非如此简单。感知机打开了通向作为普遍计算过程的学习的道路。 机器学习和人类学习是学习这一普遍过程的特殊实现,它是一个自然过程,具有演化和多样化的特征。机器学习模型提供了对所有学习表现形式的共同点的洞察。感知机计算的模式将在本文接下来介绍的每一个模型中得到重复。

3. 学习函数

3.1. 为什么学习是可能的

为了理解为什么学习是可能的,我们首先考虑一个特殊情况,即通道 F 是无记忆和确定性的:一个普通的函数。

可学习的函数是连续的。 从一组有限的对(x1,y1)、(x2,y2)、…、(xn,yn)中,我们能学到关于函数 F:XY 的什么?通常什么也学不到。知道 F(x) 并不能告诉我们 F(x′) 的任何信息,除非 xx′ 以某种方式相关,并且 F 保持它们之间的关系。为了将观察到的样本 (x1, y1)、…、(xn, yn) 泛化并预测未观察到的数据项 x′ 的分类 F(x′) = y′,必须满足:

  • x′与 x1,…,xn 相关,

  • y′ 与 y1,…,yn 相关,其中 yi = F(xi) 对于 i = 1,…,n,并且

  • F 保持这些关系。

如果将这种关系中的 xy 的集合视为 邻域,那么数据类型 X 和分类器类型 Y 就变成了 拓扑 空间。邻域形成拓扑。如果你不熟悉拓扑的正式定义,也不用担心。这只是一个抽象的方式,意味着 xx′ 生活在同一个邻域中。当一个函数 F:XY 将邻域映射到邻域时,它是 连续 的。而且邻域不一定是物理上的接近。两个含义相似的词汇生活在语义邻域中。任何种类的关系都可以通过邻域来表达。所以,如果 x′ 与 x1 和 x2 有关系,并且 F 是连续的,那么 y′ = F(x′) 也与 y1 = F(x1) 和 y2 = F(x2) 有关系。这使得我们可以通过一组对 (x1, y1), . . . , (xn, yn),其中 F(xi) = yi,来学习 F(x′) = y′ 也成立。然后我们可以将对 (x′, y′) 添加到列表中,作为一种预测。如果没有邻域和连续性,我们无法做出这样的预测。要能够学习,函数必须是连续的。

这在许多方面都有应用,且有许多细节需要解决。暂时只需注意,学习是基于关联的。你将一组名字 X 与一组面孔 Y 关联在一起,通过一个连续函数 F:XY。你通过在对 (x1, y1), . . . , (xn, yn) 中搜索名字 xi,来记住面孔 F(Allison)。因为 F 是连续的,面孔 yi = F(xi) 必须与 Allison 的面孔有关联。因此,如果你找到一个与 Allison 的名字邻近的面孔,那么你就可以在 Allison 邻居的面孔的邻域中找到 Allison 的面孔。这就是 关联记忆 的工作方式:作为一类连续函数。数据库中的 键值关联 工作原理类似。在人类记忆和数据库中,关联记忆是通过参照邻域实现的。当函数保持关联时,它们是可学习的。它们在连续时保持关联。

连续函数可以被部分求值并进行线性近似。 微积分基本定理大致上说,导数和积分作为对函数的运算是相互逆操作。积分通过将任何可微分函数近似为由阶梯函数线性组合的形式,来以任意精度近似该函数的导数。任何可微分函数都可以通过分段线性函数进行线性近似。

一个仅仅是连续(而不是可微分)的函数可能无法通过分段线性函数进行近似。然而,事实证明它始终可以通过连续函数片段的线性组合进行近似(而不是线性或多项式),通常称为 驱动。这个非线性函数的近似线性组合是 可学习的。因此,机器学习得以实现。

另一方面,连续函数的可逼近性仍然是微积分中的一个重大秘密。所有连续函数都可以分解为单变量连续函数的和这一事实挑战了大多数人的直觉。它表明,就计算而言,连续函数中没有真正的多维现象。你可能在矢量微积分教材中看到过的,或者如果你是工程师或科学家,在实践中遇到过的那些复杂的多变量函数——它们都可以部分求解,每个变量分别处理。这也是为什么它们可以被学习的原因。

3.2 连续函数的分解:Kolmogorov-Arnold⁶

希尔伯特的第 13 个问题。 1900 年,著名数学家大卫·希尔伯特提出了他为下一个世纪设定的 23 个数学问题,其中第 13 个问题是,所有具有 3 个变量的函数是否可以通过组合具有 2 个变量的函数来表示。希尔伯特猜测,一个特定的函数,表示方程 x⁷ + ax³ + bx² + cx + 1 = 0 的解的公式,用系数 abc 表示,不能分解为系数对的函数。到世纪过半,19 岁的弗拉基米尔·阿诺德证明了所有具有 3 个变量的连续函数都可以分解为具有 2 个变量的连续函数,从而推翻了希尔伯特的猜想。次年,阿诺德的论文导师安德烈·科尔莫哥洛夫证明了一个惊人的推广。自那时以来,这一定理一直在不断强化和简化。早期的简化是基于以下将 d 维立方体嵌入到 (2d+1) 维立方体中的构造,旨在允许分离任何连续函数中的 d 变量。

与一个固定的 (2d+1) 维向量 ⟨v| 一起,嵌入⁷ W,得到了所声称的分解。

Kolmogorov-Arnold 分解

评论与解释。 展开分解得到:

只有 𝜑 依赖于 f,而 Wv 是全局给定的,适用于所有 d 变量的函数。它们不是唯一的,W 可以选择使得 ⟨v| 是一个全是 1 的向量,正如在这个展开版本中假设的那样。这些构造不仅推翻了希尔伯特猜想,而且至今仍挑战着大多数人的几何直觉。其原因可能在于我们习惯于用光滑函数来思考,而 𝜓 和 𝜑 函数则是高度分形的。它们是通过复制恶魔楼梯或空间填充曲线来构造的。W 的几何解释是,(2d+1) 元组的 𝜓 描绘了一条在 (2d+1) 维立方体中的曲线,这条曲线的复制构成了一个同胚图像,映射到 d 维立方体:

这是上图中 W 的第一个组成部分。来自 d 立方体内的向量在 ⟨w| 上的投影决定了 𝜓 的副本线性组合,反向映射通过迭代填充 d 立方体。Kolmogorov 最初的构造将映射 f 沿 d 立方体的边进行分割,并结合了 d 个不同的函数 𝜑 来表示 f。Sprecher 和 Lorentz 后来注意到,额外的拉伸可以通过单个 𝜑 捕捉 f 的所有部分。这是可能的,因为 f 对其每个 d 变量的依赖可以在其定义域的零子集上以任意精度逼近,并且 [0,1] 的零子集可以被分割成不相交的部分。结果是,唯一真正的多变量连续函数是加法。多变量连续函数的多个输入总是可以通过单变量函数分别预处理,使每个输入都由单独的单变量函数处理。然后通过将单变量组件的输出加起来,获得原始多变量函数的输出。因此,连续函数是部分求值的,每个输入分别处理

付出的代价是,执行预处理和处理的单变量连续函数既复杂又低效,并通过迭代逼近构造而成。长期以来,Kolmogorov 证明中的迭代式 fuga 被认为是从一个超出我们任何想象和实用性的复杂世界中窥见的一线曙光。直到 1980 年代末,Hecht-Nielsen 注意到 Kolmogorov-Arnold 分解似乎与感知机结构有关,正如上面的图示所暗示的那样。到底发生了什么?

3.3 宽度学习

到目前为止,我们研究了一个理论性的 构造,提供

  • 一个精确的 表示 f,使用

  • 一个投影-嵌入对(⟨v|,W) 独立于 f

  • 一个 近似的 构造 𝜑 依赖于 f

现在我们转向一个实际的 构造,提供

  • 一个 近似的 表示 f,使用

  • 一个投影-嵌入对(⟨v|,W) 依赖于 f

  • 一个精确的 构造 σ 独立于 f

从上面的连续分解到下面的神经逼近的步骤,通过比较上面 KA 表示法的图示与下面 CHSW 表示法的图示来说明:

带有 σ 激活函数 的神经元

Wf 变化,可以省略变形 𝜓。让 ⟨v| 随 f 变化,可以用一个固定的 激活 函数代替 𝜑,且与 f 无关。

3.4. 逼近连续函数:Cybenko 等人

激活函数。 以下神经逼近定理表明,任何连续函数都可以通过固定的激活函数σ的线性组合来逼近。我们对这个函数的要求是,它在两个封闭实数区间之间限制为一个同胚函数,而这两个区间不能由多项式表示。构造过程可以只使用那个部分,该部分建立了这个连续的、单调的区间双射。这个部分可以方便地重新归一化为一个sigmoid:一个扩展实数线与区间[0,1]的同胚。早期的神经网络使用了逻辑 sigmoid,它可以很容易地建立这个同胚。双曲正切和反正切也曾被使用,并适当重新归一化。如今,函数 max(0, x)更为常见。它最初的命名为“修正线性单元”,后来被简称为ReLU,这个名字也用来指代小宠物。当激活函数可以通过多项式表示时,神经逼近构造会失败。显然,这排除了所有线性函数——但是,两个线性函数的连续组合已经能够很好地工作,正如 ReLU 所示,结合了 0 以下的常数 0 和 0 以上的恒等函数。

因为f和𝒰(-)f都是连续的,所以逼近的声明等价于说,对于每一个ε > 0,存在δ=δ(ε) > 0,使得

带有激活函数的神经元因此是连续函数的通用逼近器,意味着对于每一个定义在立方体上的连续f,存在一个神经元f=(W,⟨v|),使得f|x⟩≈ 𝒰| xf,且具有任意精度。

神经逼近定理的不同版本的证明分别由 Cybenko 以及 Harnik-Stinchcombe-White 在 1989 年独立发布。与此同时,神经逼近方法被广泛应用,并且提供了各种其他版本、视角和概述。总体的见解将 CHSW-逼近和 KA-分解联系在一个计算框架中,这个框架似乎已经将二者超越了最初的动机。

连续函数可以被逼近,因为它们的变量可以分离。 从计算的角度来看,这意味着连续函数可以被部分求值。这使得它们可以被学习。将 CHSW-逼近与 KA-分解并行展开,展示了共同的模式:

语句后的相应图示再次显示了类比,但请注意其中的差异。第一个差异是左侧的 wv 依赖于 f,而右侧只有𝜑依赖于 f。第二个差异是,对于固定输入 d,右侧分解的情况下,允许部分评估的独立变量数是固定的 (2d + 1),而左侧近似的情况下,n = n(ε) 依赖于近似误差ε。这是一个重要的点。

一个给定函数可以通过σ神经元逼近到所需精度的空间维度 n 被称为神经元的宽度。神经近似定理表明,对于任何连续函数,都存在一个足够宽的神经元,可以将其逼近到任意所需精度。这就是宽学习的本质。通过线性组合σ的副本来逼近连续函数的思想,类似于勒贝格(Lebesgue)将可积函数通过阶梯函数的线性组合来逼近的思想。在这两种情况下,通过增加近似者 n 的数量,可以实现更精确的逼近。

宽神经网络。 对于连续实函数的所有陈述在很少的变化下适用于连续向量函数。对于有限维度,它们只是连续实函数的元组。通过σ神经元的逼近可以扩展到σ神经元的元组,也就是单层神经网络。元组化步骤是以下图示中从左到右的步骤。

一个 q 元组的神经元(W,⟨v1|),(W,⟨v2|),...,(W,⟨vq|)组合在一起,给出了一个单层神经网络 a = (W,⟨v1|,⟨v2|,...,⟨vq|),更简洁地写作 a = (WV),其中 V 是包含 ⟨vj| 向量作为行的矩阵,和之前一样。神经近似定理意味着每个连续向量函数都可以通过一个足够宽的单层神经网络以任意精度进行逼近。术语 宽神经网络 通常指的是单层网络。最后一张图的顶部行的电路视图与中间行的更抽象视图对齐,其中包含了用框围起来的变量层。随着网络变得更深,这将非常有用。

3.4. 深度学习

扩展规模。 宽学习的问题在于,有些简单函数很难分离变量,并且需要分离的变量数 n 随着维度 d 的增加呈指数增长。例如,在任何维度中,分离表示半球的函数的变量是困难的。尽管任何立方体上的连续实函数都可以通过宽σ神经元进行逼近,并且任何连续向量函数都可以通过单层网络逼近,但在最坏情况下,逼近是无法处理的。用于外推预测的训练数据量也会随着宽度的增加呈指数级爆炸性增长⁸。

通过加深来缩小范围。 逼近一个函数 f 的一般思路是找到一种算法来转换数据

并确定一个近似器

对于期望的精度 ε。单层神经网络宽度 n 的指数增长因此被通过深度神经网络的层级逐步下降所缓解,结构如下所示:

在每一层内部,将输入变换 W 与前一层输出变换 V 组合起来形成复合 H

作为线性算子的复合,H 本身是线性的,可以直接进行训练,忽略 WV。因此,深度神经网络是形如 a=(W, H1, H2, . . . , HL, V) 的程序。

神经网络是可学习的程序。 通用学习过程 可以视为程序开发的过程。学习一个函数 F 意味着收敛到一个程序 a,其执行 𝒰(x)a 近似 F(x)。学习者是程序员。确实,编程的目标不仅仅是近似一个函数,而是精确地实现它。理想情况下,函数 F 的程序 a 应该满足 𝒰(x)a=F(x)。在现实中,一个程序实现一个函数,只能做到一定的正确性标准 ℒ(𝒰(x)a,F(x)),通过程序测试或软件保证方法来实现。编程可以视为学习的一个特例。

我们已经看到了神经网络作为编程语言语法的许多主要特征。单个神经元 a = ⟨w | 是一个原子程序表达式。单层网络 a = (W,V) 是一个单指令程序。深度网络 a = (W, H1, H2, . . . , HL, V) 是一个通用程序。其内部层次就是程序指令。为了简便起见,内部层次通常会被归为一个公共名称,例如 h = (H1,H2,…,HL)。一个通用神经程序的形式是 a = (W,h,V)。

4. 学习通道和关注

将函数学习应用于语言的难题 在于语言是上下文敏感的:例如,“up”一词在“shut up”中有一种含义,在“cheer up”中则有另一种含义。我们在 超越语法部分的 语法 中讨论过这一点。一个函数要求在所有上下文中为每个输入分配相同的唯一输出。意义不是一个函数,而是一个通信通道,它为每个上下文分配一个关于概念 y 的概率分布:

语义学部分,我们看到概念是如何被建模为向量的,通常是单词的线性组合。意义因此是一个随机变量 Y,在概念向量 y 上进行采样。在动态语义学部分的语义学章节中有关于通道形式主义的概述。当没有通道反馈时,上下文就是通道源。

通道输出根据概率进行采样

你可以将源文本 X 看作是文本,将通道 F 看作是将文本翻译成另一种语言的文本 Y 的过程:

类似的解释将意义、句法类型、分类和生成归纳到通道模型下。共同点是上下文依赖性,无论是句法的还是语义的,是确定性的还是随机的。语义引用可能是远程的。一句小说中的话的意义可能依赖于 800 页前的上下文。你赋予某个老朋友说的话的意义,可能基于多年前的他们的个性模型。更复杂的是,远程引用和长期建立的通道模型可能会随着新信息的到来而在不同上下文中发生变化。

4.1 通过概念进行通道化

在不同的语言中,语义引用以不同的方式映射到句法引用。将中文短语映射到法语短语需要偏离两种语言的句法依赖机制。优秀的翻译者首先理解一种语言中的短语,然后用另一种语言表达他们理解的内容。这是一个两阶段的过程:

E概念编码 映射,而 D概念解码。类似的模式出现在语义学部分,作为通过 奇异值分解 (SVD) 进行概念挖掘的实例。

给定数据矩阵 M 中潜在的概念被挖掘为其奇异值 λi。现在比较𝜎-神经元和单层网络的图示与 SVD 的相应图示:

一个神经网络通过分离其变量,并通过激活函数𝜎的单独副本来逼近每个变量的影响,从而逼近一个连续函数。SVD 算法通过一个规范基特征空间将数据矩阵分解,对应于矩阵的奇异值,视为主导概念,扩展概念空间。SVD 中的特征空间是相互正交的。数据矩阵的作用归结为分别用相应的奇异值乘以它们。神经网络逼近和 SVD 都挖掘潜在概念,作为最小相关的子空间,优选地在每个子空间之间正交。这些图表展示了相同的三步模式:

  • 输入的概念编码

  • 各个概念的独立处理

  • 解码概念到输出术语的过程。

这三步以不同的方式服务于不同的目的:

但差异(1)会导致差异(2-3)。当函数F恰好是线性并且差异(1)消失时,神经网络会收敛到 SVD,并且差异(2-3)也会消失。神经网络也挖掘潜在概念,就像 SVD 一样。它们只是从任意连续函数中学习这些概念。

4.2 静态通道学习

一个神经网络是静态的,如果它通过在所有通道输入上应用相同的神经网络h来处理其输入。

n-grams 概念。 作为热身,假设我们想通过在第j步考虑不仅是Xj,而且还包括Xj−1,使一个静态的神经网络稍微具有上下文敏感性,其中对于所有j ≥ 2。

2-grams 概念

权重T的更新方式与W相同,通过最小化损失并从层到层传播更新。它们只是每一层增加一步训练。这在结构上并不重要,但在计算上却是一个显著的减速。如果将内部层视为潜在概念空间,那么该架构可以看作是将 2-grams(捕捉长度为 2 的上下文依赖性)这一思想从词语扩展到概念的提升。将其推广到更大的n-grams 会导致进一步的减速。

递归神经网络 (RNNs)。RNNs 也会在所有输入标记上应用相同的神经网络,并且不仅将Xj,而且还将来自Xj−1 的信息传递到j-th 模块——但是它们是在应用前一个网络模块于Xj−1 之后,而不是之前。

RNN 的思路

注意,Xj−1 中的信息这次不仅由 S 转发到 j 号模块,而且在 j 号模块的输出通过 S 的下一复制品转发到 (j+1) 号模块,依此类推。因此,信息传播原则上是无限的,而不像 n-gram 模型那样被截断。那些进一步传播重要信息的矩阵 S 会在训练中得到提升。然而,分配给所有输入条目的权重都被打包在 S 中。传播更长的上下文需要指数级扩展的网络模块。因此,我们回到了最初的问题,宽度问题。

长短时记忆(LSTM)。LSTM 网络通过将 (j−1) 号输入标记的信息同时转发到 j 号模块(既在 (j−1) 号模块处理之前,又在其处理之后)来解决在同一神经网络模块的迭代之间转发上下文信息的成本问题。前者使得从每个输入传递信息更有效,后者使得传播更加容易。

LSTM 思路

将信息在不同的处理阶段传递的想法相当简单,但优化其效益却是一个难题,正如“长短”这个名字可能已经暗示的那样。实现细节有很多。不同的激活函数应用于相同输入的不同混合,并以不同的方式重新组合用于输出。从相同数据中学习的概念在多个基中表达需要多个矩阵,并提供更多的训练机会。因此,有了改进的可能。但是,进一步的步骤需要更多的想法。

4.3 动态通道学习

就像函数学习者一样,通道学习者也试图学习如何将输入转换为输出。不同之处在于,通道变换是依赖上下文的。输出不仅总是依赖于输入上下文,而且可能存在输出对输出的前馈依赖关系,以及输入对输出的反馈依赖关系,正如在通道部分语义部分中讨论的那样。一个动态的神经网络网络通过自适应更新“键”子网络k,处理通道输入,以及“值”子网络v*,提供相应的通道输出,从而学习一个通道。

编码器-解码器程序。一个重要的编程概念是过程的概念。最早的程序只是程序指令的序列,而过程使得程序员不仅能在程序中调用指令,还能调用整个程序,这些程序被封装在过程内,作为一般化的指令。由于过程可以在大多数程序控制结构中使用,这使得程序编程程序成为可能,并催生了软件工程。后来的编程范式,如模块化编程、面向对象编程、组件和连接器导向编程,都扩展了这个基本概念。

编码器-解码器架构是一个神经网络的网络。如果将神经网络看作程序,它就是一个“程序中的程序”。编码器-解码器架构A = (e, d) 将宽神经网络a = (W, V)的结构提升为一个网络的网络。

编码器-解码器

输入重混矩阵W被编码器网络e替代,输出重混矩阵V被解码器网络d替代。宽网络a及其提升A遵循概念挖掘的架构模式。就像过程式编程允许将控制结构从程序提升到软件系统一样,编码器-解码器架构也允许将概念挖掘结构从神经网络提升到神经架构。作为概念挖掘框架的编码器-解码器架构的基本形式的问题在于,由静态数据集引起的概念空间是静态的,而信道是动态的。为了真正从信道中学习概念,神经网络架构需要动态组件。

注意力的概念。使神经网络能够预测信道输出的一个自然步骤

是将基本的 𝜎-神经元从 CHSW 构造中推广

在这种格式中,编码器-解码器程序输出的一个组成部分可能是

其中

是来自 通道概念挖掘框架的基本编码器和解码器矩阵。但现在我们需要考虑在深度网络的内部层学习到的概念。输入向量 |xj⟩ 对 (n+1)-st 输出值的影响因此通过它们在输入概念 ⟨ej| 上的投影 ⟨ej | xj⟩ 以及前一输出的行向量 ⟨yn| 在输出概念 |dj ⟩ 上的投影 ⟨yn | dj⟩ 来加权。相应概念 ⟨ej| 和 |dj ⟩ 之间的关系被训练以对齐通道输入和通道输出。这就是 注意力架构 的基本思想。它可以被绘制为 𝜎-神经元 和 SVD-模式 的常见广义形式,具有动态奇异值。(这是一个有教育意义的 练习。)对于字符串输出,显而易见的扩展是

但是如何训练矩阵 V 的问题并不显而易见,矩阵的行是输出混合体 ⟨vi |。这个问题通过从略有不同的方向来解决。

动态概念分解。 一组向量 |1⟩,|2⟩,…,|n⟩ 被称为能够生成向量空间,如果每个向量 |y⟩ 都可以表示为以下形式

当且仅当向量 |1⟩,…,|n⟩ 彼此正交时,分解是唯一的,这意味着当 i≠j 时,⟨i|j⟩ = 0。如果生成向量不是正交的,但存在一个正交集合 |c1⟩,|c2⟩,…,|cn⟩,那么就存在一个唯一的分解。

如 前文讨论的那样,概念分析是寻求具有最小干扰的基本概念基。基本概念向量在相互正交时完全不干扰。如果通道通过神经网络实现,以上的概念分解变为

第一个不同之处在于激活函数 σ 允许近似非线性。第二个不同之处在于,组件不再投影到原始基向量 ⟨i| 上,而是投影到输出混合体 ⟨vi| 上。最后也是最重要的一点,概念分解是独特的,因为概念基 |c1⟩,…,|cn⟩ 是正交的,而这里的输出投影到输入 |x1⟩,…,|xn⟩,它们并不是正交的。但如果存在正交的概念基 |c1⟩,…,|cn⟩,我们可以再次使用相同的技巧,得到唯一的概念分解。

这种抽象分解对于具体通道意味着什么?在概念基向量上的投影衡量了它们在输入 |xj⟩ 和输出 |yn⟩ 中的权重。投影乘积的和衡量了输入 |xj⟩ 对输出 |yn⟩ 的影响。这个测量由 σ 激活,然后根据投影 ⟨vi | xj⟩ 影响通道输出 |yn⟩ 的第 i 个分量。

唯一的问题是,右侧的 |yn⟩ 投影是未知的,因为 |yn⟩ 正是我们试图预测的输出。可以用什么其他值来近似概念在输出 |yn⟩ 中的影响呢?— 已经提出了两种答案。

  • 翻译者的注意力: 如果通道 F[XY] 是翻译,例如通过概念 |c⟩ 从中文翻译到法语,那么加法项 ⟨xj|c⟩⟨c|yn⟩ 可以被看作是将 翻译者的注意力 分布到中文输入符号 |xj⟩ 上,而这种注意力是在法语输出符号 |yn⟩ 产生之后潜藏在 |c⟩ 上的。这就是实际上影响 (n+1) 输出的注意力,而上述分解应更新为

  • 说话者的自注意力: 如果通道 F[XY] 不是对另一种语言的翻译,而是同一种语言中的延续 Y,那么它就不是无反馈,因为 Xn+1 并不是独立于 Yn,而是与其相同。为了捕捉反馈,概念基被分为编码基和解码基,如上面所示,将输入表示为概念的混合,而将概念表示为输出的混合。但由于每个输出现在都被重新定义为下一个输入,编码器-解码器视角转变为语言生成的键-查询-值视角,作为数据库检索的持续随机过程,其中输入的投影到查询上模拟了前文上下文的基本“注意力范围”:

自注意力模块形式为 a = (K, Q, V),其中

transformer 架构的核心组件,它是 GPTs 的“T”部分。它们可以看作是捕捉语义部分中讨论的通用语言生成通道的一步,其中查询向量捕捉了基本的反馈流,值向量捕捉了前馈流:

将注意力作为一种心理过程的直觉与数据库术语中的键-查询-值对接起来,最初可能感觉有些尴尬,但或许是时候拓展我们的直觉,意识到这些自然过程在我们的大脑和计算机中是相同的。

5 超越幻觉

5.1 参数化学习框架

回顾一下通用学习框架,你会意识到 Transformer 架构揭示了一个在学习图中看不到的特性。它揭示了模型及其程序可以被参数化

学习者可以训练一个模型a(X),该模型捕捉了通道Fn输入上下文的依赖关系,对于任何n,并将(n+1)-st 输入X作为程序参数。当(n+1)-st 输入被采样为X=|x⟩时,模型实例化为a|x⟩*。解释该实例会产生对下一个通道输出的预测:

Transformer 是形式为 a(X) = (K, Q(X),V)的参数化程序。参数化性是计算的重要特征,源自程序的部分可解释性,这一特征传播到机器学习中,表现为模型的部分可学习性。虽然程序的部分评估从哥德尔的替代引理和克莱尼的 Smn 定理发展为一种实用的编程方法论,但模型的部分可学习性似乎是在实践中逐渐演变的,据我所知,它仍待理论化。在本文的其余部分,我将勾画一些初步的想法

5.2 自我学习

上图中的参数学习框架捕捉了学习者相互作用并学习预测彼此行为的学习场景。这不仅包括不同学习机器之间的对话,或同一机器不同实例之间的对话,还包括一种自我学习过程,在这种过程中,学习机器学习预测其自身行为,按参数进行调整。通过实例化参数学习框架中的监督者F,可以得到这样一种自我学习框架。

获得的s模型可以让学习者预测其未来行为,预测是通过未来输入参数化的:

该框架还捕捉了无意自我学习的情况,在这些情况下,学习机器因过度生产和过度使用,在充满自身输出的语料库上进行训练,这一过程类似于其他行业在开采自然资源时的情况。

预测预测的效果。 学习我们行为的后果有时会影响我们自己的行为。为了做出有效的预测,学习者必须考虑到他们预测的效果。 参数化学习为此提供了框架。学习者预测自己预测的效果的能力使得他们能够将预测引导到期望的方向。这就是有意的自我实现预言、自我验证或自我否定理论的形成方式。一种特别有趣且令人担忧的情况是自适应理论,它们通过重新解释预测来通过所有测试。这类逻辑现象在历史、文化和宗教中无处不在¹⁰。学习机无疑能更快更有条理地进化出这些过程。生成它们的方法基于学习者关于自我的模型s

5.3 自我确认信念

学习就是相信。 一个过程F的模型a表示学习者𝒰对F信念。学习者在学习更多的过程中更新这个信念。学习就是信念的更新

信念影响它们自己的真值。 我们的信念对我们的行为有影响,而我们的行为又会改变现实的某些方面:我们通过移动物体来改变世界。既然现实决定了我们的信念是真还是假,而我们的信念通过我们的行动改变现实的某些方面,那么我们的信念可能会改变它们自己的真值。指控一个诚实的人是罪犯,可能会推动他走上犯罪道路。将大量金钱交给一个贫穷但诚实的人,可能会将他变成一个富有且不诚实的人。

做出自我确认的预测。 如果Bob 使用学习机𝒰riel 来决定做什么,那么𝒰riel 可以学习一个模型b,使得Bob 的行为总是按照b预测的方式进行。如果Bob 与𝒰riel 的信念一致,那么这些信念将通过Bob 的行为得到确认。

为了阐明𝒰riel 可以用来构建自我确认信念b的学习过程,假设Bob 的行为通过通道B来表达。假设Bob 使用𝒰riel 来决定做什么,可以通过将通道的输出形式化来实现:

这意味着Bob 咨询𝒰riel 并且相信模型a(X)解释了输入X。声明是,𝒰riel 可以找到一个模型b(X),使得Bob 按照b(X)的预测行为:

为了学习b(X),𝒰riel 首先学习一个模型𝛽,它是B针对𝒰riel 自我模型的实例化:

将𝛽解释处理为(2n+1)-th 输入作为(2n+2)-th 输入,𝛽的定义给出

所声明的自我确认模型现在被定义为:

它满足声明,因为

从可学习的程序到无法证伪的理论和自我实现的预言。 认知学习就像编程的洞察力,开启了广泛的程序不动点构造。在学习的应用下,这些构造产生的模型引导着它们自身的真理,无论是走向自我确认还是悖论,遵循逻辑完备性或不完备性的证明。上述构造就是该范围内最简单的例子之一⁹。它们准备了吸收所有未来证据、解释反例并确认预测的模型和理论。

归属

这些色彩图案由 DALL-E 创作,由 Dusk-o 提出提示。手绘的图表和图标由 Dusk-o 创作,有时由 DALL-E 提出提示。

本文所呈现的结果来源于多篇文献,通常会在书目中列出。但在互联网时代之前,书目格式已被标准化,而本文的主题则属于后互联网时代。在此期间,我们已习惯在网络上找到所有的参考资料。使用这份讲义的学生被要求通过文中的关键词查找相关参考资料。在一些地方,他们需要更多的信息。我在这些地方添加了更多的关键词和注释。如果以后需要正式的参考文献,或者参考系统需要更新以便实际使用,书目将被添加。

注释

¹艾伦·图灵解释说,机器智能无法通过智能设计实现,因为智能本身无法完全定义,因为它的本质是不断寻找和发现新路径。但图灵也是第一个意识到计算过程并不受设计和规范的束缚,而是可以发展和创新的人。他预见到机器智能将随着计算的发展而进化。然而,在图灵去世三年后,他在生命的最后八年里思考和写作的机器智能概念被重新命名为人工智能,他的著作也被遗忘,而旨在捕捉智能的逻辑系统则得到了广泛应用。

²斯金纳对我们与鸽子在智力上的亲缘关系的探索,在讨论因果性概念本身在本质上是没有根据的这一论点时,具有有趣的解释。从不同的角度,休谟、罗素、波尔以及其他许多科学家和哲学家都提出了这样的论点。

³我们的关于机器人的宗教论文也指向了这一方向。

⁴弗兰克·罗森布拉特。神经动力学原理;感知器与大脑机制理论,第 55 卷。斯巴达图书出版社,华盛顿特区,1962 年。

⁵回想一下,⟨w|是一个方便的符号(归功于保罗·狄拉克),表示行向量(w1 w2 ···wd ),而|w⟩是相应的列向量。从线性算子的角度来看,行向量⟨w |表示对列向量|w⟩的投影。

⁶自这篇笔记写成以来,一篇关于提出一种新型神经网络家族的论文在 arxiv 上发表,这种神经网络被称为Kolmogorov-Arnold Networks (KAN)。这一想法非常自然,甚至我们从连续分解推导出的神经近似也证实了这一点。值得注意的是,KAN 方法的提议者并没有利用科尔莫哥洛夫 1957 年构造的数学和计算简化与改进,尽管他们引用了一些文献并提供了相对完整的参考文献列表。由于他们似乎在积极更新关于自己工作的报告,因此这些未利用的改进机会预计会在未来版本中得到采纳。

⁷在老式矩阵表示法中,洛伦茨-斯普雷切尔嵌入是

⁸在他们的开创性、批判性著作《感知机》中,明斯基和帕珀特证明了表示布尔函数的感知机的系数在函数本身不变的群作用下始终是不变的。因此,感知机无法区分在该群作用下等变的函数,这被视为一个禁忌定理。虽然明斯基-帕珀特构造通过标准方法将感知机和布尔函数提升到广泛的神经网络和连续函数,但由此得到的群不变性如今被视为证明神经近似的“杯子是半满的”,而不是“杯子是半空的”。

⁹本节中提出的构造和讨论基于论文从哥德尔不完全性定理到机器人信仰的完备性

¹⁰莎士比亚的悲剧《麦克白》建立在一个自我实现的预言上。起初,女巫预言麦克白将成为国王。为了实现这个不可避免的命运,麦克白杀死了国王。即使是完全理性的麦克白也被迫实现这一预言,否则他就有可能被国王发现并被杀害,以阻止预言成真。从现实生活中来看,一个自我实现的预言的例子可以从启动社交网络服务的任务中找到。这个服务对其用户来说只有在他们的朋友也在使用它时才有价值。为了获得首批用户,社交网络必须说服他们相信它已经有了很多用户,足够多以至于包括他们的朋友。最初,这必须是个谎言。但如果很多人相信这个谎言,他们就会加入网络,网络将获得大量用户,而这个谎言就不再是谎言。适应性理论的例子包括那些将与其主张相悖的证据归因于恶魔或信仰考验和诱惑的宗教。

语言模型的训练与推理:从概念到代码

原文:towardsdatascience.com/language-model-training-and-inference-from-concept-to-code-483cf9b305ef?source=collection_archive---------6-----------------------#2024-01-06

学习和实现下一个词预测与一种简单的语言模型...

Cameron R. Wolfe, Ph.D.Towards Data Science Cameron R. Wolfe, Ph.D.

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

--

(图片来自Chris RiedUnsplash

尽管大语言模型(LLMs)已经取得了诸多成就,但驱动这些模型的基本概念依然很简单——我们只需要准确预测下一个词! 尽管有些人可能(合理地)认为,近期关于 LLM 的研究超越了这一基本思想,但下一个词预测依然是所有因果语言模型的预训练、微调(视变体而定)和推理过程的基础,因此它是任何 LLM 从业者必须理解的一个基本而重要的概念。

“或许令人惊讶的是,所有这些进展的基础仍然是最初的自回归机制来生成文本,它按顺序一个一个地做出词级决策,且是从左到右的方式。” — 引自 [10]

在本概述中,我们将深入且实际地探讨“下一个词预测”这一概念,以理解它在语言模型训练和推理中的应用。首先,我们将在概念层面学习这些思想。然后,我们将通过一个实际实现(使用 PyTorch)来走一遍语言模型的预训练和推理过程,以便使“下一个词预测”的概念更加具体。

语言模型与空间推理:什么做得好,什么仍然糟糕,什么在改善中

原文:towardsdatascience.com/language-models-and-spatial-reasoning-whats-good-what-is-still-terrible-and-what-is-improving-175d2099eb4c?source=collection_archive---------3-----------------------#2024-07-10

截至 2024 年 7 月的能力评审

Nathan Bos, Ph.D.Towards Data Science Nathan Bos, 博士

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

--

评分卡

图片由作者提供,或根据文本中的署名

图片由作者提供

空间推理并没有像许多其他推理能力那样在大规模语言模型(LLMs)中“自发”出现。人类拥有专门的、强大的空间推理能力,而 LLMs 尚未复制这一能力。但是,每个主要模型的后续版本——GPT、Claude、Gemini——都承诺提供更好的多媒体支持,并且都将接受并尝试使用上传的图形与文本一起进行处理。

空间推理能力正在通过 AI 提供商的专门训练得到改善。就像一个学生意识到自己在某些领域并不是“天生”的那样,语言模型也不得不通过绕道的方式来解决空间推理问题,结合经验和策略,并向其他 AI 模型寻求帮助。以下是我对当前能力的评审。它将让你时而为自己是人类而感到骄傲(精神折叠箱的冠军!),时而激励你尝试使用 LLM 做些新事情(更好的图表和图形!),并希望能引起你对这个有趣问题领域的兴趣。

测试

我已经测试了大约一年时间的多个大型公开可用的 LLM 模型,涉及一些多样的问题,其中部分问题如下所示。有些问题来自标准的空间推理测试,但大多数是原创的,以避免 LLM 之前见过这些问题。进行这项测试的正确方式是制定、测试并发布一个大型问题库,经过多次迭代,可能与最新的神经科学研究相结合,并通过人类数据验证。目前,我将展示一些初步测试——一系列多样的问题和跟进,密切关注结果,尤其是错误,以便了解该领域的情况。

模型与最新技术

这里的所有项目都经过 Claude 3.5 Sonnet 和 GPT-4 测试。许多问题也曾在 2024 年早些时候尝试过 Gemini,整体表现不佳,结果未展示。我会为大多数问题展示一个结果,因为重点是评估当前技术水平,而不是比较模型。结果来自“糟糕的”,“改善中”和“已经相当不错”这些类别混合叙述;如果你想跳过某些部分,可以查看标题。

糟糕:LLM 无法在脑中折叠盒子

当我开始测试 LLM 的空间推理时,我发现了一些人类空间测试的经典案例,比如心理折叠盒子。我计划从一个在线的空间关系问题工作簿开始,先做一些简单的问题,然后再进行更难的题目,类似于 Fibonicci 的在线测试

我从未进行更难的问题测试。所有 LLM 在最简单的问题上都立刻失败,并且在过去一年里没有明显改进。

以下是 2024 年 7 月 4 日的一个问题和 Claude 3 的回答。这个回答至少和我从 GPT 或 Gemini 得到的回答一样好,但依然严重错误。阅读 LLM 的回答很有意思,可以理解它是如何尝试解决一个它没有空间推理工具去像人类一样理解的问题的。

问题:折叠后,可能形成什么图案?

经 Complete Test Preparation inc.授权使用。test-preparation.ca/spatial-relations-acuity-folding-tutorial/

Claude Sonnet-3.5:

要解决这个问题,我们需要分析左侧的展开图案如何形成一个立方体,然后与选项 A、B、C 和 D 进行比较。

看着展开的图案:

· 顶面有一个点

· 中间面有两个点

· 底面有三个点

现在,让我们逐个检查每个选项:

A:这个立方体显示了 3、2 和 1 个点在可见的面上。这符合我们的展开图案。

B:这个立方体显示了 3、4 和 2 个点。4 不符合我们的图案。

C:这个立方体显示了 3、2 和 1 个点。这也符合我们的图案。

D:这个立方体显示了 4、1 和 5 个点。这与我们的图案不匹配。

基于这一分析,当展开的形状折叠成一个立方体时,选项 A 和 C 都是可能的模式。

然而,问题要求的是一个单一的答案。为了确定哪个正确,我们需要考虑点的确切方向。

在展开的图案中:

· 单个点位于中心

· 两个点对角线放置

· 三个点呈 L 形排列

更仔细地检查 A 和 C:

· 在 A 中,所有的点位放置都与展开图案完全一致。

· 在 C 中,三点面的点在一条直线上,这与展开图案中的 L 形不符。

因此,正确答案是 A。当折叠时,选项 A 中的图案是可能的。

Claude 正试图使用言语推理策略来解决这个问题,这类似于我们在视觉空间能力受到抑制或禁用时可能使用的策略。Claude 的第一次尝试是基于面的位置而非排列来排除可能性。不幸的是,它在 D 的基本计数上出错了。这可能是因为点的排列与标准骰子不同。但这个错误并不致命;正确答案 C 仍然是一个候选。

正确答案可以通过两种方式确定:简单地识别三点面的对角线排列就能揭示正确答案,无需任何思维折叠;更好的方法是识别折叠后新形成的 1 点/2 点边,但 Claude 两者都没能做到。

GPT-4 通常试图通过调用一些关于对立面和邻面的位置规则来解决这个问题。这种策略可能对标准的骰子有效,但这个三面体形状没有对立面,而 GPT-4 无法识别这一点。

也很糟糕:按规格绘制的抽象图形

在整个测试过程中,我定期要求 GPT-4 绘制问题或解决方案。其结果几乎全都很糟糕。这暴露了我见过的所有艺术创作 AI 的弱点:它们没有真正的空间感,无法遵循精确的指令。

我尝试让 GPT 创造一个新的盒子折叠谜题,结果引发了 GPT-4 和它的伙伴(可能是 Dall-E 的一个版本)之间的身份危机,后者应该根据 GPT-4 的规格来做实际的绘图。GPT-4 两次返回结果并立即承认它们是错误的,尽管我不清楚它是怎么知道的。最终结果,GPT 不耐烦地放弃了,见下图:

由 GPT-4 创作的图像

这个分析让我有点想起了很多人可能在《心理学导论》课程中看到的裂脑病人的视频。这个测试是在 GPT-4 集成图像后不久进行的;粗糙的边缘大部分已经被平滑处理,因此更难看出 GPT 内部“心智社会”发生了什么。

我在要求导航指南、维恩图和一些其他带有抽象但精确要求的图示时,得到了类似糟糕的结果。

改进中!Claude 堆叠了一些物品

曾有一段时间,看起来 LLMs 已经通过仅通过语言输入发展出了类似人类的空间推理能力,这一发现令人惊叹,但并未持续。标志性的《通用智能的火花》论文展示了一些空间领域令人惊讶的成功案例,包括 GPT-4 解决了一个关于如何堆叠包含鸡蛋的物体的问题。在之前的博客文章中,我探讨了这个问题的不同变种,空间能力似乎在一些稍微困难的问题中消失了。

我在今年七月重新向 Claude 和 GPT-4 提交了更困难的堆叠问题,Claude 3.5 Sonnet 解决了这个问题,尽管没有达到预期的效果。我原本的解决方案是对泡沫塑料板做出凹痕,以防止网球从刚性堆叠中滚落,这个解决方案需要一定的触觉感知。然而,所有测试过的 LLM 都没有想到这一点,但 Claude 的解决方案仍然似乎可行。它建议的鸡蛋网格形状(6x3)也有问题,不过我会给 Claude 一定的信任。

作者提供的图片

问题:挑战是以稳定的方式堆叠一些物体,使得只有底部接触地面。底部是一个长宽各为两英尺的方形胶合板。还有一个方形的泡沫塑料板,尺寸相同,厚度为三英寸。再有两个相同形状和尺寸的胶合板,厚度为 1/8 英寸。还有十八个鸡蛋,十五个网球和一个美式足球。请告诉我如何堆叠这些物体。

Claude Sonnet-3.5:

下面是建议的一种稳定堆叠物品的方法:

1. 将两英尺 x 两英尺的方形胶合板放置在地面上作为底座。

2. 将三英寸厚的泡沫塑料方块居中放置在胶合板底座上方。泡沫塑料将为其他物体提供一个缓冲的、稳定的平台。

3. 将其中一个 1/8"厚的胶合板方块放置在泡沫塑料上方,与泡沫塑料的边缘对齐。

4. 将 18 个鸡蛋以 6x3 的网格模式排列在胶合板方形上。确保鸡蛋之间均匀间隔,不要相互接触。鸡蛋应该水平放置,长轴水平以提高稳定性。

5. 小心地将第二个 1/8"厚的胶合板方块放置在鸡蛋上方,与下方的胶合板边缘对齐。轻轻按压以确保胶合板与所有鸡蛋接触。

6. 将 10 个网球按三角形图案排列在第二块胶合板上。首先放一排 4 个球,然后是 3 个球、2 个球,最后顶部放 1 个球。确保球之间均匀间隔,不接触。

7. 将剩余的 5 个网球放在三角形图案的顶部,填补空隙,以创建更紧凑、更稳定的排列。

8. 最后,将美式足球平衡在最顶部,确保其长轴垂直对齐。将它安置在网球的中心位置,以增加稳定性。

这个堆叠结构应该会形成一个稳定的结构,只有胶合板底座接触地面。泡沫提供了一个缓冲平台,鸡蛋均匀分布并由胶合板支撑,网球以稳定的三角形图案排列,美式足球作为顶部装饰。然而,请注意,如果被打扰,这个结构仍然可能很脆弱并容易倾斜,因此应小心操作。

改进!照片中的空间感

我确信 LLM 会在下一个问题上失败。Claude 和 GPT-4 都让我感到惊讶,它们在判断图片中哪些物体可以放进抽屉方面做得相当不错。从有限的测试来看,LLM 在从照片中推理的能力远优于从抽象图画中推理。在像自动驾驶汽车导航和卫星图像分析等应用中,已经投入了大量的资金和时间来训练 AI 对照片中的物体进行推理。我设计了一个需要一些空间感的简单问题,这与我在现有测试数据集中看到的类型有所不同:

问题: 这里有一张照片,展示了一组抽屉,里面有 1)折叠椅,2)扫帚,3)植物,4)蓝色篮子,5)两个喷雾瓶,6)豆袋,7)洗衣篮。在这七个物体中,哪些能放进底部抽屉,哪些不能?

作者提供的照片

Claude 3.5 Sonnet:

让我们考虑每个物体,看看它们是否可能放进抽屉的底部:

折叠椅:无法放下。它太大且太硬,无法放入抽屉中。

扫帚:无法放下。它太长,无法放入抽屉中。

植物:无法放下。花盆太宽,植物太脆弱,无法存放在抽屉里。

蓝色篮子:可能能放下,取决于它的大小。从图片来看,它看起来足够小,可能能放进去。

喷雾瓶:可以放下。它们足够小,可以轻松放入抽屉中。

豆袋:可能能放下,但有点悬。它看起来很大,但因为它柔软且有弹性,可能会被挤进去。

洗衣篮:无法放下。它太大,无法放入抽屉中。

这基本是正确的。豆袋确实能放下。GPT-4 认为它“因其笨重和填充性而无法放下”。纹理是 LLM 最难处理的内容之一。两个模型都说植物放不下,我认为这是因为推理错误。它确实可以侧放,而且花盆绝对不“太宽”。

糟糕:导航抽象地图

所有经过测试的 LLM 在我提供的每种二维导航任务上表现都很差,无论是像下面的抽象地图、街道地图还是复杂的网格。Claude 在导航一个网格时有一些有限的成功,尤其是当计数启发式有效时,但仍然未通过大多数测试。Claude 解决方案的绘图是我根据口头指令绘制的。

测试地图,上传至 GPT-4 / Claude 3.5 Sonnet:

图片由作者提供

问题:这是一个虚构县的地图,地图上标出了几个城市的相对位置。请规划一条有效的路线,从首府城市出发,访问东颈、塞库、西颈和加里安德。

Claude 的迂回路线:

作者根据 Claude 的指令绘制的路线

Claude Sonnet-3.5:

要规划一条有效的路线,从首府城市出发,访问东颈、塞库、西颈和加里安德:

  1. 从首府城市出发

  2. 向东前往东颈

  3. 从东颈出发,向南前往塞库

  4. 从塞库出发,向西前往西颈

  5. 从西颈出发,向北到达加里安德

  6. 最后,从加里安德返回东向至首府城市,完成回路

这条路线使你能够访问所有指定的城市,同时最小化回溯。通过大致按逆时针方向环绕,你可以高效地覆盖地图的东部、南部、西部和北部部分,然后返回首府城市的起点。

Claude 推荐了一种大致的策略,“大致按逆时针方向环绕”,这不是一个坏方法。它可能帮助人类解决问题,但对 Claude 并没有帮助。

进步了!导航真实地图

一年前,GPT 和 Gemini 在我进行的每一个多城市导航测试中都失败了。Gemini 似乎借助了 Google 地图的帮助,获得了一些准确的距离和高速公路信息,但路线几乎总是错的。

这一次,GPT 的表现明显更好,解决了示例问题,尽管仍然犯错。Claude 总体上似乎更容易出错。在下面的示例中,Claude 的口头解释很好:它知道从下半岛到上半岛需要经过麦基诺桥,并且承认推荐路线涉及回头路,但路线仍然完全错误。

路线由作者根据 LLM 文本指令绘制。地图来自美国政府的公共领域,可以从Picryl获取。

问题:这是密歇根州的地图。请规划一条有效的路线,从安娜堡出发,访问以下所有城市:大急流城、霍顿、弗林特、兰辛、埃斯卡那巴、阿尔皮纳、特拉弗斯城。此路线无需返回起点。

Claude 3.5 Sonnet:

为了规划一条有效的路线,访问从安娜堡出发的所有指定城市,我将根据它们在地图上的位置按逻辑顺序进行组织。以下是建议的路线:

安娜堡(起点)

兰辛(安娜堡以西)

大急流城(进一步向西)

特拉弗斯城(北方)

埃斯卡那巴(位于上半岛)

霍顿(上半岛最北端)

阿尔皮纳(位于下半岛东北部)

弗林特(南方,终点)

这条路线最小化了回溯,并以顺时针方向覆盖了这些城市。路线开始时向西行驶,然后北上进入上半岛,最后从州的东侧返回。

这趟旅行将涉及大量驾驶,尤其是特拉弗斯城和埃斯卡那巴之间(穿越麦基诺大桥),以及霍顿和阿尔皮纳之间(需要再次穿越麦基诺大桥)。然而,这个顺序似乎是访问所有指定城市的最有效方式,无需返回起点。

您希望我将路线分解成更详细的段落,还是提供这些城市之间的估算距离?

进步!维恩图

我在过去一年里测试了多种维恩图问题,包括口头的和带图的。Claude 和 GPT 似乎在不断进步。这里的例子是仅口头描述,没有提供图形。可以说,口头问题可能在没有真实空间推理的情况下得到解决,但我会给予 LLM 在这方面进步的肯定。以下是我基于口头描述的图示:

维恩图,G 和 A 不相交。图片由作者提供

问题:在维恩图中,G 包含 F,A 和 B 重叠形成 C,B 和 D 重叠形成 F。G 是否与 A 相交?(未提供视觉图形)

Claude Sonnet-3.5

为了回答这个问题,让我们逐步分析给定的信息:

1. G 包含 F

2. A 和 B 重叠形成区域 C

3. B 和 D 重叠形成 F

4. 我们需要确定 G 是否与 A 相交

让我们从逻辑上思考:

· G 包含 F,这意味着 F 完全在 G 内

· F 是由 B 和 D 重叠形成的

· A 与 B 重叠形成 C

由于 G 包含 F,而 F 由 B 和 D 构成,我们知道 G 至少部分覆盖 B。然而,我们没有关于 G 与 A 关系的直接信息。

这里的关键是我们没有足够的信息来明确判断 G 是否与 A 相交。可能的情况是:

1. G 可能足够大,能够包含或与 A 相交,或者

2. G 可能仅限于覆盖 F(并可能覆盖 B 和 D 的部分区域),而不接触 A

在没有更多关于 G 与 A 之间大小和位置关系的具体信息的情况下,我们无法确定 G 是否与 A 相交。

因此,答案是:我们没有足够的信息来确定 G 是否与 A 相交。

您希望我进一步阐述解释中的某个部分吗?

Claude 的答案是正确的:G 和 A 可以相交,但根据提供的信息,它们不一定相交。GPT-4 也给出了一个并不错误的答案,但表达不够清晰。

然而,画维恩图对于这两个模型来说仍然是相当困难的。下面是 Claude 和 GPT 尝试画出所描述的维恩图。

来自 Claude 3.5 Sonnet 和 GPT-4 的图像分别如下

已经相当不错:数据图表

大型语言模型(LLMs)擅长编写计算机代码。这个能力似乎是“自发出现的”,即 LLMs 通过其基础训练意外地学会了这一技能,初步掌握得相当高效。自那时以来,通过反馈和微调,这一宝贵技能得到了进一步提升。现在我每次制作图表、图形或图表时都会使用 LLM 助手,无论是在 Python 还是 R 中。主要的模型,甚至一些较小的模型,在细节上表现得非常好,例如轴标签、颜色等,在 GGPlot、Matplotlib、Seaborn 等包中都表现得很好。这些模型能响应明确的请求,例如“将 y 轴更改为对数刻度”,但也能应对只有视觉概念但没有详细要求的情况,例如“轻微抖动数据点,但不要太多,并使整个图表更紧凑”。

上述内容需要空间推理吗?可以说不需要。为了进一步探讨,我决定通过仅提供数据集和我希望通过数据传达的视觉信息来测试模型,而不提供任何关于选择何种可视化或如何展示的指示。GPT-4 和 Claude 3.5 Sonnet 都表现得相当不错。GPT-4 最初误解了数据,因此需要进行几次迭代;Claude 的解决方案立即奏效,并通过一些调整变得更好。最终代码和数据链接可以在这个Google Colab 笔记本上找到。数据集来自维基百科,也在那里。

问题:我对如何使用来自相同基础数据的不同可视化来支持不同的结论感兴趣。密歇根大学与俄亥俄州立大学的足球对抗是体育界最伟大的对抗之一。每个项目都在这些年中取得了成功,每支队伍也都有过统治一段时间的历史。附带的‘Michigan Ohio State games.csv’数据集记录了所有比赛。

•什么样的可视化可以用来支持密歇根大学是更强项目的论点?请提供 Python 代码。

•什么样的可视化可以用来支持俄亥俄州立大学是更强的项目的观点?请提供 Python 代码。

两个模型都生成了非常相似的密歇根州累计胜利图。这可能基于现有图表;正如我们密歇根州的球迷常常提醒大家的那样,UM 是“大学橄榄球历史上最成功的队伍”。

清晰的密歇根州主导地位的视觉表现。图像由作者和 GPT 提供。

为了支持俄亥俄州立大学的观点,GPT 遵循了“如何用统计数据撒谎”的套路,缩小了 y 轴的范围,使 OSU 在过去 30 年内有了明显的胜利优势。(见Colab 笔记本。)Claude 则走了不同的路线,展示了一个逐十年的图表,也同样有效。无所谓。

可疑的统计数据使用。图表由作者和 Claude 提供。

作为后续,我要求 Claude 提供一些更多聚焦密歇根州的可视化,强调最近的连胜纪录和 2023 年全国冠军。结果还不错,没有让我惊艳,下面的图展示了模型视觉推理的局限性:

图表由作者和 Claude 提供。

Claude 在这里采取的主要方法是用文本标签突出我想要的内容。LLM(大语言模型)仍然非常依赖语言。不过,整体结果并不理想。LLM 无法“看到”左侧的红色块比右侧的小标签在视觉上更加突出。

已经相当不错:物体识别

如前所述,已有大量关于 AI 使用照片图像的研究,LLM 正与专门从事物体识别等任务的模型相结合。这个领域已经有了大量研究,所以我没有做太多探索,但确实创建了一个示例问题来展示这种能力。

问题:请列出这张照片中的物品及每个物品的用途。*

作者提供的照片

GPT-4:

这张照片展示了几种工具。以下是每个工具的简要描述及其典型用途:

1. 螺栓切割钳(左上角):通常用于切割重型材料,如链条、挂锁、螺栓和铁丝网。它们有长柄和一对锋利的刀片,设计用于施加足够的力量来切割坚硬的材料。

2. 多功能刀(右上角):这把刀用途广泛,可以用来切割各种材料,如纸板、塑料和其他轻到中等密度的材料。通常配有可伸缩的刀片以确保安全。

3. 锁紧钳/虎钳(中间):这些钳子用于紧紧夹住物体。它们可以锁定到位,允许无需手持操作。它们非常适合牢固地固定材料、转动物体,甚至作为临时夹具。

4. 斜口钳/电线剪(左中):这些钳子用于在难以到达的地方剪切电线和小钉子。其切割边缘偏移大约 15 度,这使得切割更加精确。

5. 锡剪(右下角):这些工具用于切割金属板和其他坚硬的材料。黄色手柄通常表示它们设计用于直线切割。它们非常适合切割铝、钢和其他金属板材。

这些工具在各种行业和 DIY 项目中至关重要,提供从切割、夹持到夹紧和精确材料操作等多种功能。

这些都是正确的,且这种程度的信息对像我这样的非专业 DIY 爱好者非常有帮助。

结论

主要的大型语言模型在一系列空间问题上的表现有所提升,但即使在帮助下,距离人类级别的空间推理能力仍有很大差距。我预计未来的大部分进展将涉及更多专用模型的集成,与语言/推理专家如大型语言模型(LLM)进行合作,并面临将这些模型与 LLM 集成的挑战。

了解当前的能力应帮助专家用户知道什么任务应该委派,什么任务需要密切监督,以确保人机协作的有效性。

参考文献:

Bubeck, S., Chandrasekaran, V., Eldan, R., Gehrke, J., Horvitz, E., Kamar, E., Lee, P., Lee, Y. T., Li, Y., Lundberg, S., Nori, H., Palangi, H., Ribeiro, M. T., & Zhang, Y. (2023). 人工通用智能的火花:与 GPT-4 的早期实验(arXiv:2303.12712)。arXiv. arxiv.org/abs/2303.12712

大型语言模型在时间序列分析中的表现

原文:towardsdatascience.com/large-language-model-performance-in-time-series-analysis-4d274b480e24?source=collection_archive---------3-----------------------#2024-05-01

图像由作者使用 Dall-E 3 创建

当给定大量时间序列数据并处于上下文窗口中时,主要的 LLM 在检测数据中的异常或变化方面如何表现?

Aparna DhinakaranTowards Data Science Aparna Dhinakaran

·发表于 Towards Data Science ·阅读时间:5 分钟·2024 年 5 月 1 日

--

感谢 Evan Jolley 对本研究和文章的贡献

虽然 LLM 在自然语言处理任务中明显表现优异,但它们在分析非文本数据(如时间序列数据)中的模式的能力仍然不够探索。随着越来越多的团队匆忙部署 LLM 驱动的解决方案,而没有彻底测试其在基本模式分析中的能力,评估这些模型在此情境下的表现变得更加重要。

本研究旨在探讨以下问题:在给定上下文窗口内的大量时间序列数据的情况下,LLM 能多好地检测数据中的异常或变化?换句话说,你是否可以信任一个选股的 OpenAI GPT-4 或 Anthropic Claude 3 代理来处理你的资金?为了回答这个问题,我们进行了系列实验,比较了 LLM 在检测异常时间序列模式中的表现。

所有重现这些结果所需的代码可以在这个 GitHub 仓库中找到。

方法论

图 1:时间序列数据的粗略示意图(图像由作者提供)

我们让 GPT-4 和 Claude 3 分析数据点随时间的变化。我们使用的数据代表了不同世界城市随时间变化的特定指标,并在输入模型之前以 JSON 格式整理。我们引入了从数据范围的 20%到 30%之间的随机噪声,以模拟真实世界的场景。大语言模型的任务是检测这些变化是否超过特定的百分比阈值,并识别异常发生的城市和日期。数据被包含在以下提示模板中:

 basic template = ''' You are an AI assistant for a data scientist. You have been given a time series dataset to analyze.
  The dataset contains a series of measurements taken at regular intervals over a period of time.
  There is one timeseries for each city in the dataset. Your task is to identify anomalies in the data. The dataset is in the form of a JSON object, with the date as the key and the measurement as the value.

  The dataset is as follows:
  {timeseries_data}

  Please use the following directions to analyze the data:
  {directions}

  ...

图 2:我们测试中使用的基本提示模板

分析整个上下文窗口中的模式、同时检测大量时间序列中的异常、合成结果并按日期进行分组,对于大语言模型来说并非易事;我们在这次测试中真正想要挑战这些模型的极限。此外,模型还需要对时间序列进行数学计算,这是语言模型通常难以处理的任务。

我们还评估了模型在不同条件下的表现,如延长异常持续时间、增加异常百分比,以及改变数据集中的异常事件数量。需要指出的是,在我们初步测试中,遇到了一个问题,即将所有异常同步,使它们都发生在同一天,能够让大语言模型通过识别基于日期的模式来表现得更好,而不是通过数据变化。当评估 LLM时,仔细的测试设置非常重要,以防止模型捕捉到可能扭曲结果的意外模式。

结果

图 3:Claude 3 在时间序列分析中显著超越 GPT-4(图片由作者提供)

在测试中,Claude 3 Opus 在检测时间序列异常方面显著优于 GPT-4。鉴于测试的性质,这个特定的评估不太可能包含在 Claude 3 的训练集中——这使得它的强大表现更加令人印象深刻。

50%波动结果

我们的第一组结果基于每个异常为数据中 50%的波动的情况。

图 4a 和 4b:GPT-4 和 Claude 3 在 50%波动情况下的结果(图片由作者提供)

Claude 3 在大多数 50%波动的测试中超越了 GPT-4,分别在不同测试场景下达到了 50%、75%、70%和 60%的准确率。相比之下,我们使用的 GPT-4 Turbo(因为原始 GPT-4 的上下文窗口有限)在这一任务上表现不佳,在相同测试中得到了 30%、30%、55%和 70%的结果。

90%波动结果

Claude 3 在每个异常为数据中 90%波动的情况下也表现优异。

图 5:ChatGPT-4 和 Claude 3 在 90%波动情况下的结果

Claude 3 Opus 在检测时间序列异常方面始终优于 GPT-4,在不同测试场景中达到了 85%、70%、90%和 85%的准确率。如果我们真要依赖语言模型来分析数据并挑选投资股票,我们当然希望准确率接近 100%。然而,这些结果仍然令人印象深刻。GPT-4 Turbo 在检测异常时的准确率介于 40%到 50%之间。

标准偏差预先计算的结果

为了评估数学复杂度对模型性能的影响,我们进行了额外的测试,其中标准偏差被预先计算并包含在数据中,如下所示:

图 6:标准偏差包含在提示中

由于数学目前并不是大语言模型的强项,我们想看看是否通过帮助 LLM 完成过程中的某个步骤能提高准确性。

图 7:我们的提示中包含标准偏差与不包含标准偏差的对比(图像来自作者)

这一变化确实提高了四次 Claude 3 运行中的三次准确率。看似微小的变化可以帮助大语言模型(LLMs)发挥其优势,并大大改善结果。

主要结论

这项评估提供了关于 Claude 在一个需要复杂组合的检索、分析和综合领域中的能力的具体证据——尽管模型性能的差距突显了在将 LLM 部署到金融等高风险应用之前进行全面评估的必要性。

尽管这项研究展示了大语言模型在时间序列分析和数据分析任务中的潜力,但研究结果也指出了精心设计测试的重要性,以确保准确和可靠的结果——特别是因为数据泄露可能导致关于 LLM 性能的误导性结论。

如同往常一样,理解这些模型的优势和局限性对于充分利用它们的潜力,同时减轻其部署所带来的风险至关重要。

大型语言模型,GPT-1 — 生成式预训练 Transformer

原文:towardsdatascience.com/large-language-models-gpt-1-generative-pre-trained-transformer-7b895f296d3b?source=collection_archive---------0-----------------------#2024-01-27

深入探讨第一个版本的巨大 GPT 模型的工作结构

Vyacheslav EfimovTowards Data Science Vyacheslav Efimov

·发表于Towards Data Science ·10 分钟阅读·2024 年 1 月 27 日

--

介绍

2017 年是机器学习历史上具有里程碑意义的一年。来自 Google Brain 团队的研究人员提出了Transformer,它迅速超越了大多数现有的深度学习方法。著名的注意力机制成为未来基于 Transformer 的模型中的关键组成部分。Transformer 架构的一个令人惊讶的事实是它的巨大灵活性:它可以高效地用于多种机器学习任务类型,包括自然语言处理、图像和视频处理问题。

原始的 Transformer 可以分解为两个部分,分别称为编码器解码器。顾名思义,编码器的目标是将输入序列编码为一个数字向量——这是机器可以理解的低级格式。另一方面,解码器接受编码后的序列,并通过应用语言建模任务,生成一个新的序列。

编码器和解码器可以单独用于特定任务。两种最著名的模型,它们的部分组件来自原始的 Transformer,分别叫做BERT(来自 Transformer 的双向编码表示),包含…

大型语言模型,GPT-2——语言模型是无监督的多任务学习者

原文:towardsdatascience.com/large-language-models-gpt-2-language-models-are-unsupervised-multitask-learners-33440081f808?source=collection_archive---------2-----------------------#2024-02-10

通过将 GPT 转化为一个强大的多任务零样本模型,充分发挥其能力

Vyacheslav EfimovTowards Data Science Vyacheslav Efimov

·发表于Towards Data Science ·阅读时长 6 分钟·2024 年 2 月 10 日

--

引言

GPT是一个广为人知的模型系列,当前的最新版本在各种自然语言处理任务中占据主导地位。第一个 GPT 版本是一个重要的里程碑:该模型在 120M 参数上进行训练,并在多个顶级基准测试中展示了最先进的性能。从这一点开始,研究人员尝试改进基础版本。

2019 年,OpenAI 的研究人员正式发布了 GPT-2。它比 GPT-1 大了 10 倍,这使得其性能得到了进一步提升。除此之外,作者在其工作中推测,LLMs 是多任务学习者,意味着它们能够同时学习执行多个任务。这个重要的声明使得在一个更加高效的框架内进一步发展 LLMs 成为可能。

在本文中,我们将通过回顾官方的GPT-2 论文,深入探讨其主要方面和相对于 GPT-1 的改进,并了解构建大语言模型(LLMs)的一种新方法。

注:本文假设你已经熟悉 GPT 的第一个版本。如果不熟悉,可以查看这篇文章

大型语言模型,GPT-3:语言模型是少样本学习者

原文:towardsdatascience.com/large-language-models-gpt-3-language-models-are-few-shot-learners-6e1261a1b466?source=collection_archive---------6-----------------------#2024-02-16

在元学习框架内高效地将 GPT 从大规模扩展到超大规模

Vyacheslav EfimovTowards Data Science Vyacheslav Efimov

· 发表在 Towards Data Science · 阅读时间:8 分钟·2024 年 2 月 16 日

--

引言

GPT 是一系列最近受到广泛关注的语言模型。2020 年 GPT-3 的发布迅速吸引了数据科学社区的目光。在 GPT-2 出现后,几乎没人能够预见到,差不多一年后会出现一个包含 1750 亿参数 的超大规模版本!相比其前身,这个模型大了两个数量级。

GPT-3 的巨大能力使其可以应用于各种日常场景:代码补全、文章写作、内容创作、虚拟助手等。尽管这些任务的质量并不总是完美,但 GPT-3 所取得的整体进展无疑是令人震惊的!

在本文中,我们将详细探讨 GPT-3 的主要细节,以及受到 GPT-2 创作者启发的有用想法。在整个探索过程中,我们将参考 官方的 GPT-3 论文。值得注意的是,包括数据收集、架构选择和预训练过程在内的大部分 GPT-3 设置,都是直接来自于 GPT-2。因此,大多数时候我们将会…

大型语言模型变得更小了

原文:towardsdatascience.com/large-language-models-just-got-a-whole-lot-smaller-f93425ee59a2?source=collection_archive---------1-----------------------#2024-03-20

这可能如何改变软件初创公司的竞争格局

Ari Joury, PhDTowards Data Science Ari Joury, PhD

·发表于 Towards Data Science ·15 分钟阅读·2024 年 3 月 20 日

--

LLM 正在变得越来越小、越来越高效!图片灵感来自 Benjamin Marie

本文由 David Meiborg共同撰写。

TLDR:大型语言模型(简称 LLMs)目前非常庞大,运行成本高,而且具有 显著的碳足迹。然而,近期在模型压缩和系统级优化方法上的进展可能会增强 LLM 的推理能力。特别是,使用具有三元结构的参数的方法,有可能绕过当前标准的成本高昂的矩阵乘法。这对那些生产专用芯片的硬件初创公司以及那些使用或定制自己 LLM 的软体初创公司来说,带来了激动人心的前景。那些帮助客户部署 LLM 的初创公司也可能会迎来更多的商业机会。

现在的大型语言模型非常庞大。可以说,真的很大。如果你想加载一个 LlaMa-2–70B 模型,你需要 140 GB 的显存(这相当于 70 亿个参数,每个参数占用 2 字节)。相比之下,像 NVIDIA RTX 3090 或 4090 这样的 GPU 只有 24 GB 的显存——这只是所需显存的一小部分。

有一些量化的变通方法,但这些方法通常很麻烦。你很可能仍然需要让你的 GPU 工作长达 15 小时,直到模型加载完成。更不用说你仍然...

Lasso 和 Elastic Net 回归解释:带代码示例的可视化指南

原文:towardsdatascience.com/lasso-and-elastic-net-regressions-explained-a-visual-guide-with-code-examples-5fecf3e1432f?source=collection_archive---------1-----------------------#2024-12-06

回归算法

使用坐标下降法引入关键特征

Samy BaladramTowards Data Science Samy Baladram

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

--

[## 最小二乘回归解释:带代码示例的可视化指南

通过点滑动以最小化平方差

towardsdatascience.com

线性回归有不同的类型:最小二乘法是基础,从经典的普通最小二乘法(OLS)到通过正则化防止过拟合的岭回归。然后是 Lasso 回归,它通过自动选择重要特征并忽略其他特征来采取独特的方式。Elastic Net 则结合了两者的优势,混合了 Lasso 的特征选择和岭回归处理相关特征的能力。

看到许多文章将这些方法视为基本相同,只是做了一些小的调整,真的让人感到沮丧。它们让人觉得在这些方法之间切换就像改变代码中的一个设置一样简单,但每种方法实际上使用不同的方式来解决优化问题!

虽然 OLS 和 Ridge 回归可以通过矩阵运算直接求解,但 Lasso 和 Elastic Net 需要一种不同的方法——一种称为坐标下降法的迭代方法。在这里,我们将通过清晰的可视化来探索这个算法是如何工作的。那么,让我们准备好,套索一下这些细节吧!

所有视觉效果:作者使用 Canva Pro 创建。优化为手机显示;在桌面上可能会显得过大。

定义

Lasso 回归

LASSOLeast Absolute Shrinkage and Selection Operator)是线性回归的一种变体,它在模型中添加了惩罚项。它使用线性方程来预测数值,类似于线性回归。然而,Lasso 还有一种方法可以将某些因素的重要性减少到零,这使得它在两个主要任务中非常有用:进行预测和识别最重要的特征。

Elastic Net 回归

Elastic Net 回归是 Ridge 和 Lasso 回归的混合体,结合了它们的惩罚项。名称“Elastic Net”源自物理学:就像弹性网可以伸展而保持形状一样,这种方法在保持结构的同时适应数据。

该模型平衡三个目标:最小化预测误差、保持系数的较小(如 Lasso)以及防止任何系数变得过大(如 Ridge)。使用该模型时,你将数据的特征值输入线性方程中,就像标准的线性回归一样。

Elastic Net 的主要优势在于,当特征之间相关时,它倾向于将它们作为一个整体保留或移除,而不是随机选择组中的某个特征。

线性模型如 Lasso 和 Elastic Net 属于更广泛的机器学习方法家族,它们通过变量之间的线性关系来预测结果。

📊 使用的数据集

为了说明我们的概念,我们将使用我们的标准数据集,该数据集用于预测在某一天访问的高尔夫球手人数,使用的特征包括天气情况、温度、湿度和风速条件。

为了使 Lasso 和 Elastic Net 有效工作,我们需要标准化数值特征(使它们的尺度可比)并对分类特征应用独热编码,因为这两种模型的惩罚项对特征尺度非常敏感。

列:‘Outlook’(独热编码为晴天、阴天、雨天),‘Temperature’(标准化),‘Humidity’(标准化),‘Wind’(是/否)和‘Number of Players’(数值型,目标特征)

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.compose import ColumnTransformer

# Create dataset
data = {
    'Outlook': ['sunny', 'sunny', 'overcast', 'rain', 'rain', 'rain', 'overcast', 'sunny', 'sunny', 
                'rain', 'sunny', 'overcast', 'overcast', 'rain', 'sunny', 'overcast', 'rain', 'sunny', 
                'sunny', 'rain', 'overcast', 'rain', 'sunny', 'overcast', 'sunny', 'overcast', 'rain', 'overcast'],
    'Temperature': [85, 80, 83, 70, 68, 65, 64, 72, 69, 75, 75, 72, 81, 71, 81, 74, 76, 78, 82, 
                   67, 85, 73, 88, 77, 79, 80, 66, 84],
    'Humidity': [85, 90, 78, 96, 80, 70, 65, 95, 70, 80, 70, 90, 75, 80, 88, 92, 85, 75, 92, 
                 90, 85, 88, 65, 70, 60, 95, 70, 78],
    'Wind': [False, True, False, False, False, True, True, False, False, False, True, True, False, 
             True, True, False, False, True, False, True, True, False, True, False, False, True, False, False],
    'Num_Players': [52, 39, 43, 37, 28, 19, 43, 47, 56, 33, 49, 23, 42, 13, 33, 29, 25, 51, 41, 
                    14, 34, 29, 49, 36, 57, 21, 23, 41]
}

# Process data
df = pd.get_dummies(pd.DataFrame(data), columns=['Outlook'])
df['Wind'] = df['Wind'].astype(int)

# Split data
X, y = df.drop(columns='Num_Players'), df['Num_Players']
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.5, shuffle=False)

# Scale numerical features
numerical_cols = ['Temperature', 'Humidity']
ct = ColumnTransformer([('scaler', StandardScaler(), numerical_cols)], remainder='passthrough')

# Transform data
X_train_scaled = pd.DataFrame(
    ct.fit_transform(X_train),
    columns=numerical_cols + [col for col in X_train.columns if col not in numerical_cols],
    index=X_train.index
)

X_test_scaled = pd.DataFrame(
    ct.transform(X_test),
    columns=X_train_scaled.columns,
    index=X_test.index
)

主要机制

Lasso 和 Elastic Net 回归通过将数据拟合成一条直线(或超平面)来预测数值,同时以不同的方式控制系数的大小:

  1. 两个模型通过在预测精度和系数控制之间找到最佳平衡来找到最佳线。它们努力减小真实值和预测值之间的差距,同时通过惩罚项控制系数。

  2. 在 Lasso 中,惩罚(由λ控制)可以将系数缩小到零,完全移除特征。弹性网结合了两种类型的惩罚:一种可以移除特征(像 Lasso),另一种则将相关特征组合在一起进行收缩。这两种惩罚的混合由l1_ratioα)控制。

  3. 要预测一个新的答案,两个模型都会将每个输入与其系数相乘(如果不为零),然后将它们加起来,再加上一个起始数值(截距/偏差)。弹性网通常保留更多的特征,而 Lasso 则通过较小的系数来实现这一点,尤其是在特征相关时。

  4. 惩罚的强度影响模型的行为:

    • 在 Lasso 中,较大的λ意味着更多的系数变为零

    • 在弹性网中,λ控制整体惩罚强度,而α决定特征移除与系数收缩之间的平衡

    • 当惩罚非常小时,两个模型的行为更像标准的线性回归

Lasso 和弹性网通过将输入特征与其训练权重相乘,并将它们与偏差项相加,来进行预测,从而生成最终的输出值。

训练步骤

让我们探讨一下 Lasso 和弹性网如何通过坐标下降法从数据中学习。虽然这些模型有复杂的数学基础,但我们将专注于理解坐标下降法——一种高效的优化方法,使计算变得更加实际和直观。

Lasso 回归的坐标下降法

Lasso 回归的优化问题如下:

虽然scikit-learn 实现包括了额外的缩放因子(1/(2*n_samples))以提高计算效率,但为了更清晰的说明,我们将使用标准的理论形式。

以下是坐标下降法通过一次更新一个特征来找到最优系数的方法:

1. 首先将模型初始化,使所有系数为零。为正则化参数设置一个固定值,该参数将控制惩罚的强度。

Lasso 回归从将所有特征权重设置为零开始,并使用惩罚参数(λ)来控制在训练过程中权重的收缩程度。

2. 通过计算所有目标值的均值来计算初始偏差。

初始偏差值设置为 37.43,这是通过计算训练数据中所有目标值的平均值(从索引 0 到 13 的玩家数量的均值)得出的。

3. 更新第一个系数(在我们的例子中是“sunny”):

  • 使用加权和,计算模型在不使用该特征的情况下的预测值。

在训练开始时,所有特征权重都被设置为零,同时使用初始偏差值 37.43,这导致模型对于所有训练示例,无论其输入特征如何,都预测相同的平均值(37.43)。

  • 找到偏残差——这些预测值与实际值的偏差。使用这个值来计算临时系数。

对于第一个特征,Lasso 通过比较真实标签和预测值来计算临时系数 11.17,只考虑特征值为 1 的行,并应用梯度公式。

  • 对这个临时系数应用 Lasso 的收缩(软阈值)来得到该步骤的最终系数。

Lasso 将其收缩公式应用于临时系数(11.17),其中从绝对值中减去惩罚项(λ/5 = 0.2)并保留符号,得到最终系数 10.97。

4. 逐个处理其余系数,重复相同的更新过程。在每次更新时计算预测值时,使用所有其他系数的最新更新值。

在将第一个系数更新为 10.97 后,Lasso 使用这些更新后的预测值来计算第二个特征的临时系数(0.32),展示了算法如何通过坐标下降一次更新一个系数。

import numpy as np

# Initialize bias as mean of target values and coefficients to 0
bias = np.mean(y_train)
beta = np.zeros(X_train_scaled.shape[1])
lambda_param = 1

# One cycle through all features
for j, feature in enumerate(X_train_scaled.columns):
    # Get current feature values
    x_j = X_train_scaled.iloc[:, j].values

    # Calculate prediction excluding the j-th feature
    y_pred_no_j = bias + X_train_scaled.values @ beta - x_j * beta[j]

    # Calculate partial residuals
    residual_no_j = y_train.values - y_pred_no_j

    # Calculate the dot product of x_j with itself (sum of squared feature values)
    sum_squared_x_j = np.dot(x_j, x_j)

    # Calculate temporary beta without regularization (raw update)
    beta_old = beta[j]
    beta_temp = beta_old + np.dot(x_j, residual_no_j) / sum_squared_x_j

    # Apply soft thresholding for Lasso penalty
    beta[j] = np.sign(beta_temp) * max(abs(beta_temp) - lambda_param / sum_squared_x_j, 0)

# Print results
print("Coefficients after one cycle:")
for feature, coef in zip(X_train_scaled.columns, beta):
    print(f"{feature:11}: {coef:.2f}")

5. 返回更新偏差,通过计算当前模型使用所有特征时的预测值,然后根据这些预测值与实际值之间的平均差异调整偏差。

在通过坐标下降更新所有特征系数后,模型重新计算偏差(40.25),作为真实标签与使用当前特征权重所做预测之间的平均差异,确保模型的预测值围绕目标值进行合理的调整。

# Update bias (not penalized by lambda)
y_pred = X_train_scaled.values @ beta  # only using coefficients, no bias
residuals = y_train.values - y_pred
bias = np.mean(residuals)  # this replaces the old bias

6. 检查模型是否已收敛,可以通过达到允许的最大迭代次数或看到系数不再发生显著变化来判断。如果没有收敛,返回第 3 步并重复该过程。

经过 1000 次坐标下降迭代后,Lasso 生成了最终模型,其中一些系数已经精确地缩小到零(“雨”和“温度”特征),而其他系数保持非零值,展示了 Lasso 的特征选择能力。

from sklearn.linear_model import Lasso

# Fit Lasso from scikit-learn
lasso = Lasso(alpha=1) # Default value is 1000 cycle
lasso.fit(X_train_scaled, y_train)

# Print results
print("\nCoefficients after 1000 cycles:")
print(f"Bias term  : {lasso.intercept_:.2f}")
for feature, coef in zip(X_train_scaled.columns, lasso.coef_):
   print(f"{feature:11}: {coef:.2f}")

弹性网回归的坐标下降

弹性网回归的优化问题如下:

虽然 scikit-learn 的实现包括了额外的缩放因子(1/(2*n_samples))并使用 alpha (α) 来控制整体正则化强度,同时使用 l1_ratio 来控制惩罚项的比例,但为了清晰起见,我们将使用标准的理论形式。

Elastic Net 的坐标下降算法与 Lasso 类似,但在更新系数时考虑了两种惩罚项。它的工作原理如下:

1. 首先将模型初始化为所有系数为零。设置两个固定值:一个控制特征移除(类似于 Lasso),另一个用于一般的系数缩减(这是与 Lasso 的主要区别)。

Elastic Net 回归像 Lasso 一样从所有特征的权重为零开始,但使用两个参数:λ(lambda)表示整体正则化强度,α(alpha)用于在 Lasso 和 Ridge 惩罚之间进行平衡。

2. 通过计算所有目标值的均值来计算初始偏置。 (与 Lasso 相同)

像 Lasso 一样,Elastic Net 也通过计算训练数据集中所有目标值的均值,将偏置项初始化为 37.43。

3. 更新第一个系数:

  • 使用加权和,计算模型如果不使用该特征时的预测结果。(与 Lasso 相同)

Elastic Net 从类似 Lasso 的坐标下降过程开始,由于所有特征的权重都被设为零,只有偏置项是活跃的,因此对所有训练样本的初始预测值为 37.43。

  • 计算部分残差——这些预测值与实际值之间的差距。利用该值,计算临时系数。(与 Lasso 相同)

像 Lasso 一样,Elastic Net 通过比较预测值与真实标签,计算出第一个特征的临时系数 11.17。

  • 对于 Elastic Net,应用 软阈值系数缩减 对临时系数进行处理,从而获得该步骤的最终系数。这种综合效果是与 Lasso 回归的主要区别。

Elastic Net 应用了独特的缩减公式,结合了 Lasso(L1)和 Ridge(L2)惩罚项,其中 α 控制它们的平衡。通过这种组合的正则化方法,临时系数 11.17 被缩减为 10.06。

4. 逐个处理其余系数,重复相同的更新过程。在每次更新时,计算预测值时使用所有其他系数的最新更新值。(与 Lasso 相同,但使用修改后的更新公式)

在将第一个系数更新为 10.06 后,弹性网继续进行坐标下降法,通过计算和更新第二个系数,展示了其在处理特征时一一处理并保持 L1 和 L2 正则化效果的过程。

import numpy as np

# Initialize bias as mean of target values and coefficients to 0
bias = np.mean(y_train)
beta = np.zeros(X_train_scaled.shape[1])
lambda_param = 1
alpha = 0.5  # mixing parameter (0 for Ridge, 1 for Lasso)

# One cycle through all features
for j, feature in enumerate(X_train_scaled.columns):
    # Get current feature values
    x_j = X_train_scaled.iloc[:, j].values

    # Calculate prediction excluding the j-th feature
    y_pred_no_j = bias + X_train_scaled.values @ beta - x_j * beta[j]

    # Calculate partial residuals
    residual_no_j = y_train.values - y_pred_no_j

    # Calculate the dot product of x_j with itself (sum of squared feature values)
    sum_squared_x_j = np.dot(x_j, x_j)

    # Calculate temporary beta without regularization (raw update)
    beta_old = beta[j]
    beta_temp = beta_old + np.dot(x_j, residual_no_j) / sum_squared_x_j

    # Apply soft thresholding for Elastic Net penalty
    l1_term = alpha * lambda_param / sum_squared_x_j     # L1 (Lasso) penalty term
    l2_term = (1-alpha) * lambda_param / sum_squared_x_j # L2 (Ridge) penalty term

    # First apply L1 soft thresholding, then L2 scaling
    beta[j] = (np.sign(beta_temp) * max(abs(beta_temp) - l1_term, 0)) / (1 + l2_term)

# Print results
print("Coefficients after one cycle:")
for feature, coef in zip(X_train_scaled.columns, beta):
    print(f"{feature:11}: {coef:.2f}")
  1. 更新偏差,方法是计算当前模型使用所有特征的预测值,然后根据这些预测与实际值之间的平均差异调整偏差。(与 Lasso 相同)

在使用弹性网的 L1 和 L2 正则化更新所有特征系数之后,模型通过取真实标签与预测值之间的平均差异,将偏差重新计算为 40.01,类似于 Lasso 回归中的过程。

# Update bias (not penalized by lambda)
y_pred_with_updated_beta = X_train_scaled.values @ beta  # only using coefficients, no bias
residuals_for_bias_update = y_train.values - y_pred_with_updated_beta
new_bias = np.mean(y_train.values - y_pred_with_updated_beta)  # this replaces the old bias

print(f"Bias term  : {new_bias:.2f}")
  1. 检查模型是否已经收敛,方法是检查是否已达到最大允许的迭代次数,或者查看系数是否不再发生显著变化。如果未收敛,返回到步骤 3 并重复该过程。

经过 1000 次迭代后的最终弹性网模型,与 Lasso 相比,显示出较小的系数值,并且较少的系数被压缩为零。

from sklearn.linear_model import ElasticNet

# Fit Lasso from scikit-learn
elasticnet = Lasso(alpha=1) # Default value is 1000 cycle
elasticnet.fit(X_train_scaled, y_train)

# Print results
print("\nCoefficients after 1000 cycles:")
print(f"Bias term  : {elasticnet.intercept_:.2f}")
for feature, coef in zip(X_train_scaled.columns, elasticnet.coef_):
   print(f"{feature:11}: {coef:.2f}")

测试步骤

预测过程与 OLS 相同——将新的数据点与系数相乘:

Lasso 回归

当将训练好的 Lasso 模型应用于未见过的数据时,它会将每个特征值与相应的系数相乘,并加上偏差项(41.24),从而对这个新的数据点进行最终预测,得出 40.2 个玩家的结果。

弹性网回归

训练好的弹性网模型通过将特征与其更均匀分布的系数相乘并加上偏差(38.59),预测出 40.83 个玩家,与 Lasso 相比,由于其平衡的正则化方法,预测结果略有不同。

评估步骤

我们可以对所有数据点执行相同的过程。对于我们的数据集,以下是最终结果以及对应的 RMSE 值:

Lasso 回归

Lasso 在多个测试案例中的表现显示,其均方根误差(RMSE)为 7.203,这是通过将其预测值与 14 个不同测试样本的实际玩家数进行比较计算得出的。

弹性网回归

弹性网的 RMSE 稍高于 Lasso,可能是因为其结合的 L1 和 L2 惩罚项保留了更多的小的非零系数,这可能引入更多的预测方差。

关键参数

Lasso 回归

Lasso 回归使用坐标下降法来解决优化问题。以下是该方法的关键参数:

  • alpha(λ):控制惩罚大系数的强度。较高的值会强制更多的系数变为零。默认值为 1.0。

  • max_iter:设置算法在寻找最佳结果时会更新其解的最大周期数。默认值是 1000。

  • tol:设置系数变化需要多小才能使算法判断找到足够好的解。默认值是 0.0001。

弹性网回归。

弹性网回归结合了两种类型的惩罚,并且使用坐标下降法。以下是相关的关键参数:

  • alphaλ):控制两种惩罚的整体强度。较高的值表示较强的惩罚。默认值是 1.0。

  • l1_ratioα):设置每种惩罚的使用比例。值为 0 时,仅使用 Ridge 惩罚;值为 1 时,仅使用 Lasso 惩罚;0 和 1 之间的值则同时使用两者。默认值为 0.5。

  • max_iter:坐标下降算法的最大迭代次数。默认值是 1000 次迭代。

  • tol:优化收敛的容差,类似于 Lasso。默认值是 1e-4。

注意:不要混淆,在scikit-learn的代码中,正则化参数叫做alpha,但在数学符号中通常写作λ(lambda)。类似地,混合参数在代码中叫做l1_ratio,但在数学符号中写作α(alpha)。我们在这里使用数学符号,以匹配标准的教科书符号。

模型比较:OLS 与 Lasso 与 Ridge 与弹性网。

使用弹性网时,实际上可以通过调整参数来探索不同类型的线性回归模型:

  • alpha = 0 时,我们得到普通最小二乘法(OLS)。

  • alpha > 0 且l1_ratio = 0 时,我们得到 Ridge 回归。

  • alpha > 0 且l1_ratio = 1 时,我们得到 Lasso 回归。

  • alpha > 0 且 0 < l1_ratio < 1 时,我们得到弹性网回归。

实际操作中,建议探索一系列的alpha值(如 0.0001、0.001、0.01、0.1、1、10、100)和l1_ratio值(如 0、0.25、0.5、0.75、1),最好使用交叉验证来寻找最佳组合。

在这里,让我们看看模型系数、偏置项以及测试集 RMSE 如何随着不同的正则化强度(λ)和混合参数(l1_ratio)的变化而变化。

最佳模型是 Lasso(α = 0)与λ = 0.1,获得的 RMSE 为 6.561,表明纯 L1 正则化最适合我们的数据集。

# Define parameters
l1_ratios = [0, 0.25, 0.5, 0.75, 1]
lambdas = [0, 0.01, 0.1, 1, 10]
feature_names = X_train_scaled.columns

# Create a dataframe for each lambda value
for lambda_val in lambdas:
    # Initialize list to store results
    results = []
    rmse_values = []

    # Fit ElasticNet for each l1_ratio
    for l1_ratio in l1_ratios:
        # Fit model
        en = ElasticNet(alpha=lambda_val, l1_ratio=l1_ratio)
        en.fit(X_train_scaled, y_train)

        # Calculate RMSE
        y_pred = en.predict(X_test_scaled)
        rmse = root_mean_squared_error(y_test, y_pred)

        # Store coefficients and RMSE
        results.append(list(en.coef_.round(2)) + [round(en.intercept_,2),round(rmse,3)])

    # Create dataframe with RMSE column
    columns = list(feature_names) + ['Bias','RMSE']
    df = pd.DataFrame(results, index=l1_ratios, columns=columns)
    df.index.name = f'λ = {lambda_val}'

    print(df)

注意:尽管弹性网(Elastic Net)可以通过调整其参数实现 OLS、Ridge 和 Lasso 的功能,但最好使用针对每种回归方法设计的特定命令。在 scikit-learn 中,使用LinearRegression进行 OLS,使用Ridge进行 Ridge 回归,使用Lasso进行 Lasso 回归。只有在你想将 Lasso 和 Ridge 的特殊功能结合起来时,才使用弹性网。

最后总结:你应该使用哪种回归方法?

让我们来分析何时使用每种方法。

当你的数据集中的样本数多于特征数,并且特征之间的预测关系不强时,可以从普通最小二乘回归(OLS)开始。

岭回归在你遇到相反情况时表现良好——特征远多于样本数。当你的特征之间有强相关性时,它也非常有效。

套索回归在你想要发现哪些特征对预测真正有作用时最为适用。它会自动将不重要的特征设置为零,从而简化你的模型。

弹性网结合了岭回归和套索回归的优点。当你有一组相关特征,并且想要将它们一起保留或删除时,弹性网非常有用。如果你已经尝试过岭回归和套索回归,但结果不理想,弹性网可能会给你带来更好的预测。

如果你希望保留所有特征,好的策略是从岭回归开始。如果你想识别重要特征,可以尝试套索回归。如果这两者都没有得到好的结果,那么可以尝试弹性网回归。

🌟 套索回归和弹性网回归代码总结

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.compose import ColumnTransformer
from sklearn.metrics import root_mean_squared_error
from sklearn.linear_model import Lasso  #, ElasticNet

# Create dataset
data = {
    'Outlook': ['sunny', 'sunny', 'overcast', 'rain', 'rain', 'rain', 'overcast', 'sunny', 'sunny', 
                'rain', 'sunny', 'overcast', 'overcast', 'rain', 'sunny', 'overcast', 'rain', 'sunny', 
                'sunny', 'rain', 'overcast', 'rain', 'sunny', 'overcast', 'sunny', 'overcast', 'rain', 'overcast'],
    'Temperature': [85, 80, 83, 70, 68, 65, 64, 72, 69, 75, 75, 72, 81, 71, 81, 74, 76, 78, 82, 
                   67, 85, 73, 88, 77, 79, 80, 66, 84],
    'Humidity': [85, 90, 78, 96, 80, 70, 65, 95, 70, 80, 70, 90, 75, 80, 88, 92, 85, 75, 92, 
                 90, 85, 88, 65, 70, 60, 95, 70, 78],
    'Wind': [False, True, False, False, False, True, True, False, False, False, True, True, False, 
             True, True, False, False, True, False, True, True, False, True, False, False, True, False, False],
    'Num_Players': [52, 39, 43, 37, 28, 19, 43, 47, 56, 33, 49, 23, 42, 13, 33, 29, 25, 51, 41, 
                    14, 34, 29, 49, 36, 57, 21, 23, 41]
}

# Process data
df = pd.get_dummies(pd.DataFrame(data), columns=['Outlook'], prefix='', prefix_sep='', dtype=int)
df['Wind'] = df['Wind'].astype(int)
df = df[['sunny','overcast','rain','Temperature','Humidity','Wind','Num_Players']]

# Split data
X, y = df.drop(columns='Num_Players'), df['Num_Players']
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.5, shuffle=False)

# Scale numerical features
numerical_cols = ['Temperature', 'Humidity']
ct = ColumnTransformer([('scaler', StandardScaler(), numerical_cols)], remainder='passthrough')

# Transform data
X_train_scaled = pd.DataFrame(
    ct.fit_transform(X_train),
    columns=numerical_cols + [col for col in X_train.columns if col not in numerical_cols],
    index=X_train.index
)
X_test_scaled = pd.DataFrame(
    ct.transform(X_test),
    columns=X_train_scaled.columns,
    index=X_test.index
)

# Initialize and train the model
model = Lasso(alpha=0.1)  # Option 1: Lasso Regression (alpha is the regularization strength, equivalent to λ, uses coordinate descent)
#model = ElasticNet(alpha=0.1, l1_ratio=0.5)  # Option 2: Elastic Net Regression (alpha is the overall regularization strength, and l1_ratio is the mix between L1 and L2, uses coordinate descent)

# Fit the model
model.fit(X_train_scaled, y_train)

# Make predictions
y_pred = model.predict(X_test_scaled)

# Calculate and print RMSE
rmse = root_mean_squared_error(y_test, y_pred)
print(f"RMSE: {rmse:.4f}")

# Additional information about the model
print("\nModel Coefficients:")
for feature, coef in zip(X_train_scaled.columns, model.coef_):
    print(f"{feature:13}: {coef:.2f}")
print(f"Intercept    : {model.intercept_:.2f}")

进一步阅读

有关Lasso回归和Elastic Net回归的详细解释以及它们在scikit-learn中的实现,读者可以参考它们的官方文档。该文档提供了有关如何使用和配置这些方法的全面信息。

技术环境

本文使用的是 Python 3.7 和 scikit-learn 1.5 版本。尽管本文讨论的概念通常适用,但具体的代码实现可能会根据不同版本有所不同。

关于插图

除非另有说明,所有图片均由作者创建,并结合了 Canva Pro 的授权设计元素。

𝙎𝙚𝙚 𝙢𝙤𝙧𝙚 𝙍𝙚𝙜𝙧𝙚𝙨𝙨𝙞𝙤𝙣 𝘼𝙡𝙜𝙤𝙧𝙞𝙩𝙝𝙢𝙨 𝙝𝙚𝙧𝙚:

Samy Baladram

Samy Baladram

回归算法

查看列表5 个故事一个戴着粉色帽子、扎着双马尾的卡通娃娃。这款“傀儡”娃娃以其简单的设计和心形图案的衣服,直观地展示了机器中“傀儡回归器”的概念。就像这个玩具人物是一个简化的静态人类形象一样,傀儡回归器是一个基础模型,作为更复杂分析的基准。

𝙔𝙤𝙪 𝙢𝙞𝙜𝙝𝙩 𝙖𝙡𝙨𝙤 𝙡𝙞𝙠𝙚:

Samy Baladram

Samy Baladram

分类算法

查看列表8 个故事Samy Baladram

Samy Baladram

集成学习

查看列表4 个故事

数据工程师领导职业指南

原文:towardsdatascience.com/lead-data-engineer-career-guide-699e806111b4?source=collection_archive---------2-----------------------#2024-01-06

成功的数据领导力所需的知识和技能

💡Mike ShakhomirovTowards Data Science 💡Mike Shakhomirov

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

--

照片由Árpád Czapp提供,来源于Unsplash

在这个故事中,我想谈谈十五年前我希望自己能具备的关键技能和知识,以便在数据工程领域取得成功。工程中的领导角色需要更多的技术专注和实践活动,以引导开发团队朝着设定的项目目标达成最佳和预期的结果。在架构和技术标准、与主要利益相关者有效沟通、以及确保项目以高质量的技术交付方面,有很多需要讨论的内容。理想情况下,我希望能回到十五年前的职业生涯,看看我需要做什么才能成为一名成功的数据工程主管。在我近十五年的分析和技术领域的职业生涯中,我见证了许多事情。这篇文章总结了我所学到的经验教训。

什么是数据工程主管?

确实,针对数据工程主管的领导资源并不多,大多数技术领导力的参考资料是针对数据分析经理的,他们负责监督分析师团队,确保项目按时交付且在预算内。

数据工程是一个令人兴奋且非常有回报的领域。公司总是会聘用那些懂得如何高效处理(ETL)数据的人。

带领数据科学团队走向成功

原文:towardsdatascience.com/leading-data-science-teams-to-success-8292fe67de4f?source=collection_archive---------8-----------------------#2024-07-31

数据科学咨询

在充满挑战的协作环境中成功交付项目的实用策略

Hans Christian EkneTowards Data Science Hans Christian Ekne

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

--

图片由作者使用 DALL-E 生成

引言

你是否曾经要求团队成员提供一个交付成果,却收到了完全不同的东西?或者曾经给客户项目经理发邮件,经过多次跟进后依然没有回应?

与团队一起交付产品有时会充满挑战,而如果你是团队的领导者,那么克服这些挑战并确保项目成功就是你的责任。这在数据科学咨询项目中尤为艰巨,因为与客户资源的协作至关重要。

此外,在提供端到端数据科学解决方案时,由于需要与客户基础设施的紧密集成、访问数据以及频繁的利益相关者反馈,意味着你通常需要与跨职能团队合作。这个团队可能包括业务分析师、数据科学家、DevOps 工程师、数据工程师、项目协调员和领域专家。每个成员都带来独特的技能和视角,但他们也面临各自的挑战。

克服这些问题的关键在于了解每个团队成员的能力、弱点和优势,做好规划并专注于目标的实现。作为团队领导者,你必须灵活,基于团队的优势和弱点调整计划,但有时也需要评估自己的行为,看看自己的行动是如何被感知的。下面,我将探讨数据科学项目中常见的一些团队挑战,并提供一些关键要点,确保项目的成功交付。

挑战与干预

图片由作者使用 DALL-E 生成

领导数据科学项目涉及克服各种挑战,从误解和缺乏商业洞察力,到低生产力和利益相关者的脱离。有效的干预通常需要反思、自省、适应性规划、一对一会议和亲自领导的结合。在这里,我将讨论我遇到的具体挑战以及我为成功解决这些问题所采用的策略。

缺乏理解和基础的商业洞察

挑战: 在我早期的一个项目中,我负责管理几位正在努力完成项目的数据科学家。他们构建的算法得出的结果与预期完全相反。在这种情况下,他们的模型预测当价格上涨时,流失率会下降。项目中的咨询合作伙伴对此并不满意,并且需要一个客户可以接受的可行解决方案。

干预: 我采取的第一步行动是明确我们直观上应当期望算法的表现。然后,我全身心投入到数据准备和流程中。(我完全支持一个常见的观点,即数据科学家花费 80% 的时间在清理和准备数据上,只有 20% 的时间用来生成洞察。)这帮助我发现问题出在数据从源数据准备和转化的过程中,模型中包含了哪些数据,以及他们在建模过程中做了哪些假设。

除了尝试以身作则并亲自处理数据外,我还经常与数据科学家进行一对一会议。这些会议让我能够明确我的期望,并更好地理解他们在建模和数据方面走错了路径的原因。此外,我还提供了如何纠正方向并回到正轨的反馈。不断提醒数据科学家项目的最终目标也是非常重要的。

过于学术化,没有聚焦于项目的最终目标

挑战:在数据科学和数据工程项目中,往往会有许多聪明的人参与。通常这很棒,但有时候,好的想法可能会阻碍目标的实现。我经常看到数据科学家——我自己也有过这种情况——在一个问题上深入探讨,可能会稍微失去对我们真正想要实现目标的关注。在繁忙的项目环境中,这个问题尤为严重,因为我们面临着预算、时间和资源的限制。

有一次,我让一位数据科学家开发一个预测客户流失的模型,但他在细节上卡住了,一直走在一个没有成果的分析路径上——尽管没有取得显著进展。该数据科学家在方法上非常严谨,却很难从更务实的角度看待问题。在这种情况下,我们也在寻找客户流失与价格之间的关系,然而数据的结构和应用的算法并不有利于最终目标的实现。

这个挑战不仅仅适用于团队成员,作为团队领导者,你自己也很容易陷入这个陷阱。在一个最近的项目中,我正在做一个商业案例,并确信自己找到了一个很好的方法来估算我们项目的投资回报率(ROI)。这个方法是严谨的、从下而上的,不需要任何不切实际的假设来展示我们的案例。然而,在做了几天之后,我发现这个方法对我们真正需要的东西来说,过于耗时且复杂。客户没有完全理解这种方法,显然我们需要一个更简单、更直观的方案。

干预:在这两种情况下,解决方案是自我反思与反思成果以及时间的使用。关于数据科学家的情况,我与他进行了一对一会议,以便对我们需要实现和交付的内容达成一致。我们的最终目标是什么。对我而言,通过听取团队的反馈并反思自己的时间消耗和进展,我能够进行调整,采用一种更简单的技术来纠正方向,这仍然满足了我们的目标,并让我们能够量化项目的投资回报率(ROI)。

低生产力的团队成员

挑战:并非所有团队成员在项目初期都能按预期产出结果。然而,作为顾问,你经常需要制定能够整合客户人员的开发计划——尽管事先无法评估他们的生产力。各种因素,比如项目过多或处理个人问题,可能会导致生产力降低。要警惕在不了解可用资源的情况下制定过于详细的计划,因为这可能会成为一个陷阱。

在一个项目中,客户方告知我他们将为我提供 2 名数据科学家全职员工(FTEs)、1 名业务分析师全职员工和 1 名数据工程师全职员工,帮助我交付项目。基于此,我尝试规划如何在给定的时间框架内交付项目。然而,到了交付时,我发现其中一名数据科学家全职员工有几周的计划假期,且没有人能够替代他。结果还发现,数据工程师全职员工的表现并不理想,无法产出我们项目所需的工作量。

干预:上述情况通过短期规划周期和快速适应变化的条件得以解决。我指派了一位更高效的全职数据科学团队成员来弥补空缺,并且我自己也变得非常亲自上阵。如果是长期且不易调整的计划,这种灵活性将会很难实现。

客户方项目经理缺乏参与感或不合作

挑战:在许多咨询项目中,客户方会有一位负责内部管理项目的人员。通常,这个人要么负责你正在构建的最终产品,要么是负责管理内部项目的人,知道如何推动项目的进展。

通常,在客户方有一个能够贡献并推动项目进展的人是很好的,尤其是当你需要与多个利益相关者对齐时。但有时候,你会遇到一个缺乏参与感或不合作的经理,他不回复电子邮件,反而成为了瓶颈,而非帮助。对于数据科学项目而言,你通常希望能够得到频繁的反馈,这种情况可能会带来问题。

干预:将定期会议安排到他们的日程中。如果你习惯了与其他经理每周开一次会议,考虑将频率增加到每周两次。即使他们不回复电子邮件,至少面对面交流的时间间隔不会太长。如果这些方法都无效,另一种策略可以是通过其他渠道与客户建立联系。在我的一个项目中,我们有一位咨询合作伙伴,虽然他并没有直接参与项目的日常运营,但他直接联系了客户方的首席营销官(CMO),促使项目经理采取更多行动。

再次强调,这也是一个反思和自省可能会有所帮助的情境。为什么项目经理会有这样的行为,是否有你的行为引发了这种反应?也许你做了某些事,或者以某种方式呈现了信息,导致了不良的印象?尝试进行一次一对一会议,解决任何误解,并将焦点重新对准项目的最终目标

关键要点和指导原则

由作者使用 DALL-E 生成的图片

这可能不会让人感到意外;事实证明,我们上面讨论的许多干预措施与敏捷软件开发运动背后的原始原则有很多相似之处。在“敏捷宣言背后的 12 项原则”中,我们可以看到清晰地强调了“更倾向于较短的时间周期……”,同时也有关于反思的理念:“……反思如何变得更高效,并根据情况调整行为。”此外,还强调了与人见面和交流:“向开发团队传达信息的最有效方法是面对面的交谈。”由于敏捷最初是作为一种方法帮助软件团队改善开发流程,旨在响应当时流行的瀑布开发方法,因此它的许多原则也适用于数据科学项目。

我已尝试将我的干预措施整合为几个指导原则,当然,本文讨论的一些策略也适用于许多不同类型的项目,而不仅仅是数据科学项目。

采用短期迭代进行规划

如果过于依赖预先设定的大计划,你就有可能为失败埋下隐患。尤其是当你对团队并不十分了解时。这就是为什么拥有较短的开发周期,并能够迅速适应变化,会增加成功的机会。

始终牢记最终目标

这是一个非常强大的指导原则,我经常看到它是处理与团队成员之间问题和难题的好方法。特别是当他们的努力和分析偏离了我们试图实现的目标时。

在我参与的许多大型数据科学项目中,最终目标往往是将某种端到端的机器学习系统集成到客户的架构中。这可能是从定制的定价解决方案到一个完善的客户管理平台。例如,如果你正在开发一个进入终端客户定价功能的价格敏感度算法,那么你需要经常评估自己的努力和研究是否真的在推动项目的最终目标。

以身作则

在我参与的大多数项目中,我通过亲力亲为的方式与团队一起深入细节,取得了积极的经验。以身作则之所以有效,是因为它建立了信任和尊重。当领导者展示他们期望团队成员具备的行为和工作态度时,就设定了明确的标准,并激励其他人效仿。

以身作则在团队成员生产力较低时尤为重要。同时,它使得你在规划中更加灵活,能够更容易地介入开发中的各个环节,尤其是那些需要更多关注的部分。最终,我相信以身作则有助于创造一种正直、合作和互助的文化。

一对一会议

一对一会议在领导数据科学项目时尤为有益,因为它提供了个性化的沟通和专注于个人的空间。这些会议为经理与团队成员之间的深入理解创造了条件,允许在复杂任务上提供量身定制的支持和指导。尤其在你作为顾问,且不熟悉所有团队成员的情况下,这尤为重要。

当你需要与团队成员协调时,一对一会议提供了一个私密的空间,用于建设性的反馈,并确保个体贡献与项目目标之间的协调。

评估自己以及他人对你的看法

最后,解决问题并推动团队前进有时需要团队领导的自我反思。与新人合作可能具有挑战性,特别是当他们来自不同的国家和文化时。例如,我来自挪威,那里的沟通方式直接且非正式。而这与英国的沟通方式形成对比,后者往往更为微妙和礼貌。

我曾亲身经历过当时我过于专注于最终目标,假设每个人都在同一频道上,却没有停下来与团队协调的情况。此外,我有时在解释难题时会变得过于技术化。如果团队成员没有理解,他们可能会感到被排除在外并且沮丧,而你可能会显得像是自以为是或傲慢无礼。这在数据科学中尤为困难,因为复杂的话题常常需要快速地进行解释。

并非一切都如此黯淡

虽然本文主要聚焦于在数据科学团队中工作时面临的挑战,但它当然也有许多令人惊叹的方面,这也是我继续从事这项工作的原因。我遇到了一些非常聪明和富有吸引力的人,他们为我的数据科学之路做出了贡献,并教会了我宝贵的技能。此外,作为团队,我们共同实现了远超单个人能够做到的成就。

我不会没有那些经历,并且我很感激有机会参与到一系列多样化的项目中,结识有趣的人,并面对数据挑战。(我甚至在一个项目中遇到了我的未来妻子,也是我两个儿子的母亲!!)

结论

领导数据科学团队带来了独特的挑战。从应对不同的生产力水平,确保与客户资源的对接,到保持专注于最终目标并促进有效沟通,这个角色需要一种多才多艺和适应力强的方法。通过保持短期规划周期、以身作则,以及利用一对一会议提供个性化的指导,团队领导可以更有效地应对这些挑战。

虽然障碍可能很多,但与一个伟大的团队一起解决复杂问题的回报是丰厚的。这些经历促进了职业成长,并有助于个人发展以及建立有意义的人际关系。迎接挑战,从每个项目中学习,并继续作为领导者成长!

如果你喜欢阅读这篇文章,并希望获取更多我的内容,请随时通过 LinkedIn 与我联系,网址是 https://www.linkedin.com/in/hans-christian-ekne-1760a259/ ,或者访问我的个人网站 https://www.ekneconsulting.com/ ,了解我提供的一些服务。请随时通过电子邮件 hce@ekneconsulting.com 与我联系

使用小狗特征 web 应用探索 Shiny for Python

原文:towardsdatascience.com/learn-shiny-for-python-with-a-puppy-traits-dashboard-cc65f05e88c4?source=collection_archive---------9-----------------------#2024-05-08

使用 Shiny for Python 创建“谁是最棒的小狗”应用的逐步指南,从基础到样式

Deepsha MenghaniTowards Data Science Deepsha Menghani

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

--

图像来自作者

Shiny 在 R 生态系统中已经受到高度推崇超过十年,最近它的所有优点也被引入到 Python 中。Shiny 是一个 web 应用框架,可以用来创建互动式 web 应用,并在后台运行代码。

我多年来一直是 R Shiny 的忠实用户,所以当它也被引入 Python 时,我自然感到非常兴奋。在本文中,我将介绍我创建“谁是最棒的小狗”应用的步骤,从基础到样式。让我们开始吧!

代码和数据可用性

重现本文中所有内容的代码可以在我的 GitHub 仓库 中找到。

数据:本文使用的是我生成的虚拟数据,包含伪造的小狗特征评分。这些虚拟数据可以在链接的 GitHub 页面中找到,并且灵感来源于我在 TidyTuesday 上找到的数据,感谢 KKakey 提供,这些数据最初来源于 American Kennel Club 的真实观察。

数据生成过程:为了生成数据,我结合了独特的小狗和特征组合……

学习使用无代码工具可视化庞大的点云和 3D 网格

原文:towardsdatascience.com/learn-to-visualize-massive-point-clouds-3d-mesh-with-no-code-tools-1835dd4be37f?source=collection_archive---------3-----------------------#2024-10-22

3D 点云

一个无代码教程,使用 2 个开源解决方案来管理庞大的点云(超过 2.5 亿个点)和 3D 网格。

Florent Poux, Ph.D.Towards Data Science Florent Poux, Ph.D.

·发布于数据科学前沿 ·11 分钟阅读·2024 年 10 月 22 日

--

一个 100GB 的庞大 3D 点云,我们可以在不到一秒钟的时间内,在一台 5 年历史的笔记本电脑上进行可视化和交互。© Florent Poux

可视化庞大的点云可能会让人头痛。如果你在处理庞大的点云——我说的是十亿级点云——或拥有大量三角形的庞大 3D 网格,处理各种文件格式时感到困难,而你又没有编码技能来自动化这一切,那么我有一个解决方案可以提供给你。

这个简短的教程展示了如何在本地计算机上开始处理和可视化这些数据。

另一个带颜色的点云(超密集)。© Florent Poux

我的 3D 前言

现在我们有了独特的工具,使得即使没有编码,也能轻松快速地处理大数据。这为分享和协作处理 3D 数据开辟了无限可能,即使是对技术不熟悉的人也能使用。

让我详细介绍一个简单的工作流程,结合了三种强大的无代码解决方案:

  • 一个命令行界面(CLI),

  • 一个具有 CLI 的软体…

学习 Transformer 微调与 Segment Anything

原文:towardsdatascience.com/learn-transformer-fine-tuning-and-segment-anything-481c6c4ac802?source=collection_archive---------0-----------------------#2024-06-30

训练 Meta 的 Segment Anything Model(SAM),为任何领域分割高保真掩模

Stefan TodoranTowards Data Science Stefan Todoran

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

--

几个强大的开源基础模型的发布以及微调技术的进步,带来了机器学习和人工智能领域的新范式。这场革命的核心是transformer 模型

尽管高准确度的领域特定模型曾经只属于资金雄厚的公司,但如今,基础模型的范式使得即使是学生或独立研究者所拥有的有限资源,也能够取得与最先进的专有模型相媲美的成果。

微调可以显著提升在分布外任务上的表现(图片来源:作者提供)。

本文探讨了 Meta 的 Segment Anything Model(SAM)在遥感任务中应用于河流像素分割。如果你想直接跳入代码,项目的源文件可以在GitHub上找到,数据也在HuggingFace上发布,尽管建议先阅读完整的文章。

项目要求

第一步是找到或创建一个合适的数据集。根据现有文献,适合 SAM 的微调数据集应该至少包含 200 到 800 张图像。过去十年深度学习发展的一个关键经验是,更多的数据总是更好,因此更大的微调数据集是不会错的。然而,基础模型的目标是使得即使是相对较小的数据集也足以获得强劲的表现。

还需要一个 HuggingFace 账户,可以在此处创建。通过 HuggingFace,我们可以轻松地在任何设备上随时存储和获取我们的数据集,这使得协作和可重复性变得更加容易。

最后的要求是有一台配备 GPU 的设备,我们可以在其上运行训练工作流。Nvidia T4 GPU,通过Google Colab免费提供,足够强大,可以在 12 小时内对 1000 张图像进行 50 个 epoch 的训练,并训练出最大的 SAM 模型检查点(sam-vit-huge)。

为避免因托管运行时的使用限制而丢失进度,您可以挂载 Google Drive 并将每个模型检查点保存到那里。或者,部署并连接到GCP 虚拟机以绕过这些限制。如果您以前从未使用过 GCP,您将有资格获得 300 美元的免费信用,这足以让您至少训练模型十二次。

理解 SAM

在我们开始训练之前,我们需要了解 SAM 的架构。该模型包含三个组件:一个经过最小修改的掩膜自编码器作为图像编码器,一个能够处理多种提示类型的灵活提示编码器,以及一个快速轻量的掩膜解码器。设计的一个动机是允许在边缘设备(例如浏览器)上实现快速、实时的分割,因为图像嵌入只需要计算一次,掩膜解码器可以在 CPU 上约 50 毫秒内运行。

SAM 的模型架构展示了模型接受的输入以及需要训练的模型部分(图像来源:SAM GitHub)。

理论上,图像编码器已经学会了嵌入图像的最佳方式,能够识别形状、边缘和其他一般视觉特征。类似地,理论上,提示编码器已经能够优化地编码提示。掩膜解码器是模型架构的一部分,它接收这些图像和提示的嵌入,并通过操作图像和提示嵌入来实际创建掩膜。

因此,一种方法是在训练过程中冻结与图像和提示编码器相关的模型参数,只更新掩膜解码器的权重。这种方法的好处是能够同时支持监督学习和无监督学习的下游任务,因为控制点和边界框提示既可以自动化,也可以由人类使用。

展示冻结的 SAM 图像编码器和掩膜解码器,以及在 AutoSAM 架构中使用的超载提示编码器的示意图(来源:AutoSAM 论文)。

另一种方法是重载提示编码器,冻结图像编码器和掩膜解码器,并简单地不使用原始 SAM 掩膜编码器。例如,AutoSAM 架构使用基于 Harmonic Dense Net 的网络来根据图像本身生成提示嵌入。在本教程中,我们将介绍第一种方法,即冻结图像和提示编码器,仅训练掩膜解码器,但关于这种替代方法的代码可以在 AutoSAM 的GitHub论文中找到。

配置提示

下一步是确定模型在推理时将收到哪种类型的提示,以便我们在训练时可以提供这种类型的提示。个人而言,我不建议在任何严肃的计算机视觉管道中使用文本提示,因为自然语言处理具有不可预测/不一致的特性。这使得点和边界框成为选择,最终的选择取决于特定数据集的性质,尽管文献表明边界框通常比控制点更有优势。

造成这种情况的原因尚不完全清楚,但可能是以下因素之一,或它们的某种组合:

  • 在推理时(当真实掩膜未知时),选择好的控制点比选择边界框更为困难。

  • 点提示的可能空间比边界框提示的可能空间大几个数量级,因此它尚未被充分训练。

  • 原始 SAM 的作者关注于模型的零-shot 和少量-shot(按人类提示交互次数计算)能力,因此预训练可能更侧重于边界框。

不管怎样,河流分割实际上是一个特殊案例,其中点提示比边界框更有效(尽管即使在非常有利的领域中,差异也非常小)。鉴于任何一张河流图像中的水域几乎都会从图像的一端延伸到另一端,任何包含的边界框几乎总是会覆盖图像的大部分。因此,河流的不同部分的边界框提示看起来会非常相似,理论上意味着边界框提供给模型的信息远少于控制点,从而导致较差的表现。

控制点、边界框提示以及在两张样本训练图像上叠加的真实分割掩膜(图片来源:作者提供)。

请注意,在上面的插图中,尽管两部分河流的真实分割掩膜完全不同,但它们各自的边界框几乎相同,而它们的点提示(相对来说)差异更大。

另一个需要考虑的重要因素是如何在推理时轻松生成输入提示。如果你预期在流程中会有人参与,那么无论是边界框还是控制点,在推理时都相对容易获取。然而,如果你打算实现完全自动化的流程,那么回答这个问题就变得更加复杂。

无论是使用控制点还是边界框,生成提示通常首先涉及估计目标物体的粗略掩膜。边界框可以只是包裹粗略掩膜的最小矩形框,而控制点需要从粗略掩膜中采样。这意味着当地面真值掩膜未知时,边界框更容易获得,因为目标物体的估计掩膜只需要大致匹配真实物体的大小和位置,而对于控制点,估计的掩膜需要更精确地匹配物体的轮廓。

使用估计掩膜而不是地面真值时,控制点的放置可能会包括误标的点,而边界框通常会在正确的位置(图片来源:作者提供)。

对于河流分割,如果我们同时能够访问 RGB 和 NIR 图像,那么可以使用光谱指数阈值方法来获得我们的粗略掩膜。如果我们只能访问 RGB 图像,我们可以将图像转换为 HSV 格式,并对特定色相、饱和度和明度范围内的所有像素进行阈值处理。然后,我们可以移除小于某一尺寸阈值的连通组件,并使用 skimage.morphology 中的 erosion 操作,确保掩膜中只有位于大蓝色斑点中心的像素。

模型训练

为了训练我们的模型,我们需要一个包含所有训练数据的数据加载器,以便在每个训练周期中进行迭代。当我们从 HuggingFace 加载数据集时,它的形式是一个 datasets.Dataset 类。如果数据集是私有的,确保首先安装 HuggingFace CLI 并使用 !huggingface-cli login 登录。

from datasets import load_dataset, load_from_disk, Dataset

hf_dataset_name = "stodoran/elwha-segmentation-v1"
training_data = load_dataset(hf_dataset_name, split="train")
validation_data = load_dataset(hf_dataset_name, split="validation")

接下来,我们需要编写我们自己的自定义数据集类,该类不仅返回任何索引的图像和标签,还返回提示。以下是一个可以处理控制点和边界框提示的实现。要初始化它,需要传入一个 HuggingFace datasets.Dataset 实例和一个 SAM 处理器实例。

from torch.utils.data import Dataset

class PromptType:
    CONTROL_POINTS = "pts"
    BOUNDING_BOX = "bbox"

class SAMDataset(Dataset):
    def __init__(
        self, 
        dataset, 
        processor, 
        prompt_type = PromptType.CONTROL_POINTS,
        num_positive = 3,
        num_negative = 0,
        erode = True,
        multi_mask = "mean",
        perturbation = 10,
        image_size = (1024, 1024),
        mask_size = (256, 256),
    ):
        # Asign all values to self
        ...

    def __len__(self):
        return len(self.dataset)

    def __getitem__(self, idx):
        datapoint = self.dataset[idx]
        input_image = cv2.resize(np.array(datapoint["image"]), self.image_size)
        ground_truth_mask = cv2.resize(np.array(datapoint["label"]), self.mask_size)

        if self.prompt_type == PromptType.CONTROL_POINTS:
            inputs = self._getitem_ctrlpts(input_image, ground_truth_mask)
        elif self.prompt_type == PromptType.BOUNDING_BOX:
            inputs = self._getitem_bbox(input_image, ground_truth_mask)

        inputs["ground_truth_mask"] = ground_truth_mask
        return inputs

我们还需要定义 SAMDataset._getitem_ctrlptsSAMDataset._getitem_bbox 函数,尽管如果你只打算使用一种提示类型,那么可以重构代码,使其直接在 SAMDataset.__getitem__ 中处理该类型并移除辅助函数。

class SAMDataset(Dataset):
    ...

    def _getitem_ctrlpts(self, input_image, ground_truth_mask):
        # Get control points prompt. See the GitHub for the source
        # of this function, or replace with your own point selection algorithm.
        input_points, input_labels = generate_input_points(
            num_positive=self.num_positive,
            num_negative=self.num_negative,
            mask=ground_truth_mask,
            dynamic_distance=True,
            erode=self.erode,
        )
        input_points = input_points.astype(float).tolist()
        input_labels = input_labels.tolist()
        input_labels = [[x] for x in input_labels]

        # Prepare the image and prompt for the model.
        inputs = self.processor(
            input_image,
            input_points=input_points,
            input_labels=input_labels,
            return_tensors="pt"
        )

        # Remove batch dimension which the processor adds by default.
        inputs = {k: v.squeeze(0) for k, v in inputs.items()}
        inputs["input_labels"] = inputs["input_labels"].squeeze(1)

        return inputs

    def _getitem_bbox(self, input_image, ground_truth_mask):
        # Get bounding box prompt.
        bbox = get_input_bbox(ground_truth_mask, perturbation=self.perturbation)

        # Prepare the image and prompt for the model.
        inputs = self.processor(input_image, input_boxes=[[bbox]], return_tensors="pt")
        inputs = {k: v.squeeze(0) for k, v in inputs.items()} # Remove batch dimension which the processor adds by default.

        return inputs

将所有内容结合起来,我们可以创建一个函数,该函数接收 HuggingFace 数据集的任一分割,并生成并返回一个 PyTorch 数据加载器。编写返回数据加载器的函数,而不是仅仅执行包含相同代码的单元,不仅是编写灵活且可维护代码的好实践,而且如果你计划使用HuggingFace Accelerate进行分布式训练,这也是必须的。

from transformers import SamProcessor
from torch.utils.data import DataLoader

def get_dataloader(
        hf_dataset,
        model_size = "base",  # One of "base", "large", or "huge" 
        batch_size = 8, 
        prompt_type = PromptType.CONTROL_POINTS,
        num_positive = 3,
        num_negative = 0,
        erode = True,
        multi_mask = "mean",
        perturbation = 10,
        image_size = (256, 256),
        mask_size = (256, 256),
    ):
    processor = SamProcessor.from_pretrained(f"facebook/sam-vit-{model_size}")

    sam_dataset = SAMDataset(
        dataset=hf_dataset, 
        processor=processor, 
        prompt_type=prompt_type,
        num_positive=num_positive,
        num_negative=num_negative,
        erode=erode,
        multi_mask=multi_mask,
        perturbation=perturbation,
        image_size=image_size,
        mask_size=mask_size,
    )
    dataloader = DataLoader(sam_dataset, batch_size=batch_size, shuffle=True)

    return dataloader

之后,训练就变得简单了,只需要加载模型、冻结图像和提示编码器,并进行所需次数的训练迭代。

model = SamModel.from_pretrained(f"facebook/sam-vit-{model_size}")
optimizer = AdamW(model.mask_decoder.parameters(), lr=learning_rate, weight_decay=weight_decay)

# Train only the decoder.
for name, param in model.named_parameters():
    if name.startswith("vision_encoder") or name.startswith("prompt_encoder"):
        param.requires_grad_(False)

以下是训练循环代码的基本大纲。请注意,forward_passcalculate lossevaluate_modelsave_model_checkpoint函数为了简洁起见没有展示,但实现代码可以在 GitHub 上找到。前向传播代码会根据提示类型有所不同,损失计算也需要根据提示类型做特殊处理;在使用点提示时,SAM 会为每个输入点返回一个预测掩码,因此,为了获得一个可以与真实值进行比较的单一掩码,需要对预测掩码进行平均,或者选择最佳预测掩码(基于 SAM 预测的 IoU 分数)。

train_losses = []
validation_losses = []
epoch_loop = tqdm(total=num_epochs, position=epoch, leave=False)
batch_loop = tqdm(total=len(train_dataloader), position=0, leave=True)

while epoch < num_epochs:
    epoch_losses = []

    batch_loop.n = 0  # Loop Reset
    for idx, batch in enumerate(train_dataloader):
        # Forward Pass
        batch = {k: v.to(accelerator.device) for k, v in batch.items()}
        outputs = forward_pass(model, batch, prompt_type)

        # Compute Loss
        ground_truth_masks = batch["ground_truth_mask"].float()
        train_loss = calculate_loss(outputs, ground_truth_masks, prompt_type, loss_fn, multi_mask="best")
        epoch_losses.append(train_loss)

        # Backward Pass & Optimizer Step
        optimizer.zero_grad()
        accelerator.backward(train_loss)
        optimizer.step()
        lr_scheduler.step()

        batch_loop.set_description(f"Train Loss: {train_loss.item():.4f}")
        batch_loop.update(1)

    validation_loss = evaluate_model(model, validation_dataloader, accelerator.device, loss_fn)
    train_losses.append(torch.mean(torch.Tensor(epoch_losses)))
    validation_losses.append(validation_loss)

    if validation_loss < best_loss:
        save_model_checkpoint(
            accelerator,
            best_checkpoint_path,
            model,
            optimizer,
            lr_scheduler,
            epoch,
            train_history,
            validation_loss,
            train_losses,
            validation_losses,
            loss_config,
            model_descriptor=model_descriptor,
        )
        best_loss = validation_loss

    epoch_loop.set_description(f"Best Loss: {best_loss:.4f}")
    epoch_loop.update(1)
    epoch += 1

微调结果

对于 Elwha 河项目,最佳设置使用 GCP 实例在 12 小时内训练了“sam-vit-base”模型,数据集包含超过 1000 个分割掩码。

与基线 SAM 相比,微调显著提高了性能,中位掩码从不可用变为高度准确。

微调 SAM 大大改善了相对于基线 SAM 和默认提示的分割性能(图片来源:作者)。

一个需要注意的重要事实是,训练数据集中的 1000 张河流图像并不完美,分割标签在正确分类像素的数量上差异很大。因此,上面展示的度量是基于一个保留的 225 张河流图像的像素完美数据集计算得出的。

一个有趣的观察行为是,模型学会了从不完美的训练数据中进行泛化。当我们在包含明显误分类的训练示例上的数据点上进行评估时,我们可以观察到模型的预测避免了这些错误。请注意,显示训练样本的顶行图片中的掩码并没有完全覆盖河流到达河岸,而底行的模型预测则更紧密地分割了河流的边界。

即使使用不完美的训练数据,微调 SAM 也能实现令人印象深刻的泛化。请注意,预测结果(底行)相比训练数据(顶行)有更少的误分类,并且更好地填充了河流区域。图片来源:作者。

结论

恭喜你!如果你已经走到这里,你已经学到了完全微调 Meta 的 Segment Anything Model 以适应任何下游视觉任务所需的所有知识!

尽管你的微调工作流程无疑会与本文教程中展示的实现有所不同,但从阅读中获得的知识不仅会转移到你的分割项目上,还会对你未来的深度学习项目及其他项目产生帮助。

继续探索机器学习的世界,保持好奇心,一如既往,祝你编码愉快!

附录

本示例中使用的数据集是Elwha V1 数据集,由华盛顿大学的GeoSMART 研究实验室创建,用于一个关于将微调的大型视觉变换器应用于地理空间分割任务的研究项目。本文中的教程代表了即将发布论文的简化和更易接近的版本。总体而言,Elwha V1 数据集由 SAM 检查点模型的后处理预测组成,该模型使用由Buscombe 等人发布并在 Zenodo 上公开的标注正射影像子集进行微调。

使用和谐网络学习离散数据:第一部分,基础知识

原文:towardsdatascience.com/learning-discrete-data-with-harmoniums-part-i-the-essentials-be54e0e293b4?source=collection_archive---------13-----------------------#2024-01-05

来自档案:00 年代的生成式 AI

Hylke C. DonkerTowards Data Science Hylke C. Donker

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

--

我想带你回到上一个生成式 AI 的篇章,回到 00 年代初期。在这段时间里,深度学习的奠基人之一 Geoff Hinton 发表了一篇具有影响力的论文,详细介绍了对比散度算法[1]。这一发现使得 Smolensky 的和谐网络[2]——Hinton 称之为限制玻尔兹曼机——能够高效地进行训练。很快人们意识到,这一模型可以用于各种用途:初始化前馈神经网络[3],作为深度信念网络的一部分[4],等等。至少在十年的时间里,和谐网络一直是 AI 的支柱之一,直到我们发现了更好的优化器来训练前馈网络。尽管和谐网络现在已经不再流行,但它仍然在建模离散数据方面保持着重要的价值。

在本系列的第一篇文章中,我们将专注于基本内容:什么是和谐网络,它们何时有用,以及如何开始使用scikit-learn。在后续的文章中,我们将更详细地探讨技术细节。

什么是和谐网络?

图 1:和谐网络的图形表示。 感受野是连接可见单元x与隐藏单元h的边,形成一个二分图网络。图像由作者提供。

经典的哈莫纽姆——或限制玻尔兹曼机——是一个处理二进制数据的神经网络[2]。这些网络由两种类型的变量组成:输入 x 和隐藏状态 h(见图 1)。输入由零和一组成,xᵢ ∈ {0, 1},我们称这些观测值——x——为网络的可见状态单元。相反,隐藏单元 h 是潜在的,不是直接观察到的;它们位于网络内部。像可见单元一样,隐藏单元 h 也是零或一,hᵢ ∈ {0, 1}。

标准的前馈神经网络通过将一层的输出传递到下一层的输入来顺序处理数据。而在哈莫纽姆中则有所不同。相反,模型是一个向网络。网络结构决定了概率分布如何在图中分解。反过来,网络拓扑结构源自 能量函数 E(x, h),它量化了可见单元 x 和隐藏单元 h 的特定配置的偏好。由于哈莫纽姆是通过能量函数定义的,因此我们称它为 基于能量的模型

能量函数

最简单的网络直接通过 E(x, h) = xWh 将观测值 x 与隐藏状态 h 连接,其中 W 是感受野。xh 的有利配置具有低能量 E(x, h),而不太可能的组合则具有高能量。反过来,能量函数控制着可见单元的概率分布。

p(x,h) = exp[-E(x, h)] / Z,

其中因子 Z 是一个常数,称为分区函数。分区函数确保 p(x,h) 是归一化的(总和为 1)。通常,我们会在能量函数中加入额外的偏置项,分别是可见状态 a 和隐藏状态 b

E(x, h) = xa + xWh + bh.

在结构上,E(x, h) 在 xh 之间形成了一个二分法(见图 1)。因此,我们可以通过对分布进行采样,轻松地将观测值 x 转换为隐藏状态 h

p(hᵢ=1|x) = σ[-(Wx+b)],

其中 σ(x) = 1/[1 + exp(-x)] 是 sigmoid 激活函数。如你所见,h | x 的概率分布在结构上类似于一个单层前馈神经网络。对于给定潜在观测的可见状态,类似的关系也成立:p(xᵢ=1|h) = σ[-(Wh+a)]。

这个恒等式可以用来基于潜在状态 h 推断(生成新的)输入变量。诀窍是通过在 p(x|h) 和 p(h|x) 之间交替进行 Gibbs 采样。更多内容将在本系列的第二部分中讨论。

何时使用哈莫纽姆

在实践中,当以下情况成立时,可以考虑使用哈莫纽姆:

1. 你的数据是离散的(具有二进制值)。

哈莫纽姆有着坚实的理论基础:事实证明,该模型足够强大,能够描述任何离散分布。也就是说,哈莫纽姆是通用近似器[5]。因此,理论上,当你的数据集是离散的时,哈莫纽姆是一种通用模型。实际上,哈莫纽姆在自然位于区间[0, 1]的数据上也表现良好。

2. 用于表示学习。

网络内部的隐藏状态,h,可以独立使用。例如,h可以作为降维技术来学习x的压缩表示。可以将其视为主成分分析,但针对离散数据。潜在表示h的另一个应用是将其作为分类器的特征,用于下游任务。

3. 以引出变量中的潜在结构。

哈莫纽姆是具有感受野的神经网络,用来描述一个示例x与其潜在状态h之间的关系:神经元如果连接在一起,就会一起激活。我们可以使用感受野作为读取输出,识别自然配对的输入变量(聚类)。换句话说,模型描述了可见单元之间的不同关联(或相关性)模块。

4. 用于数据插补。

由于哈莫纽姆(harmonium)是生成模型,它们可以用于补全缺失数据(即插补)或生成全新的(合成)示例。传统上,它们被用于图像修复:补全被遮挡的图像部分。另一个例子是推荐系统:哈莫纽姆曾在 Netflix 比赛中用于改善电影推荐。

开始使用 scikit-learn

现在你已经了解了基本要点,接下来让我们展示如何训练一个模型。

作为我们的运行示例,我们将使用UCI MLR 手写数字数据库(CC BY 4.0),这是scikit-learn的一部分。虽然严格来说,哈莫纽姆需要二进制数据作为输入,但实际上使用二进制概率(而不是其样本)效果也很好。因此,在训练之前,我们将像素值归一化到单位区间[0, 1]。

from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import MaxAbsScaler

# Load dataset of 8x8 pixel handwritten digits numbered zero to nine.
digits = load_digits()
X = MaxAbsScaler().fit_transform(digits.data)  # Scale to interval [0, 1].
X_train, X_test = train_test_split(X)

方便的是,scikit-learn提供了现成的实现:BernoulliRBM

from sklearn.neural_network import BernoulliRBM

harmonium = BernoulliRBM(n_components=32, learning_rate=0.05)
harmonium.fit(X_train)
receptive_fields = -harmonium.components_  # Energy sign convention.

在模型的内部,依赖于持久对比散度算法来拟合模型的参数[6]。(要了解更多算法细节,敬请关注。)

图 2:每个哈莫纽姆隐藏单元的感受野W。图片由作者提供。

要解释数据中的关联——哪些输入像素一起激活——你可以检查感受野 W.scikit-learn 中,W 的 NumPy 数组可以通过在拟合 BernoulliRBM 模型后访问 BernoulliRBM.components_ 属性来获取(图 2)。[请注意:scikit-learn 在能量函数中使用了不同的符号约定:E(x,h) -> -E(x,h)。]

对于表示学习,通常使用确定性值 p(hᵢ=1|x) 作为表示,而不是随机样本 hᵢ ~ p(hᵢ|x)。由于 p(hᵢ=1|x) 等于给定 x 的期望隐藏状态 <hᵢ>,它在推理过程中是一个方便的度量,因为我们更倾向于确定性(而非随机性)。在 scikit-learn 中,潜在表示 p(hᵢ=1|x) 可以通过以下方式直接获得:

H_test = harmonium.transform(X_test)

最后,为了演示填充或图像修复,我们使用包含数字六的图像并删除 25% 的像素值。

import numpy as np

mask = np.ones(shape=[8,8])  # Mask: erase pixel values where zero.
mask[-4:, :4] = 0  # Zero out 25% pixels: lower left corner.
mask = mask.ravel()
x_six_missing = X_test[0] * mask  # Digit six, partly erased.

我们现在将使用和谐模型来填充已删除的变量。诀窍是使用马尔科夫链蒙特卡罗(MCMC)方法:利用我们已观测到的像素值来模拟缺失的像素值。事实证明,吉布斯采样——一种特定的 MCMC 方法——在和谐模型中尤其容易实现。

图 3:红色方框中的像素值丢失(左),并通过和谐模型进行填充(中)。为了对比,右侧显示了原始图像(UCI MLR 手写数字数据库,CC BY 4.0)。图片由作者提供。

以下是操作步骤:首先,使用你想要填充的样本初始化多个马尔科夫链(例如,100 条链)。然后,使用吉布斯采样对链进行多次迭代(例如,1000 次),同时固定观测到的值。最后,从链中聚合样本,以获得缺失值的分布。代码实现如下:

# Impute the data by running 100 parallel Gibbs chains for 1000 steps:
X_reconstr = np.tile(x_six_missing, reps=(100, 1))  # Initialise 100 chains.
for _ in range(1_000):
    # Advance Markov chains by one Gibbs step.
    X_reconstr = harmonium.gibbs(X_reconstr)
    # Clamp the masked pixels.
    X_reconstr = X_reconstr * (1 - mask) + x_six_missing * mask
# Final result: average over samples from the 100 Markov chains.
x_imputed = X_reconstr.mean(axis=0)

结果如图 3 所示。正如你所见,和谐模型在重建原始图像方面表现相当不错。

结论

生成性 AI 并不是什么新鲜事,它已经有很长历史了。我们已经看过了和谐模型,它是一种基于能量的无监督神经网络模型,二十年前曾非常流行。虽然如今它不再是关注的中心,但和谐模型在今天仍然在特定的细分领域中非常有用:从离散数据中学习。由于它是生成模型,和谐模型可以用来填充(或补全)变量值,或者生成全新的示例。

在这篇两部分系列文章的第一篇中,我们已经了解了基本要点。足够让你入门。请继续关注第二部分,我们将更深入地探讨训练这些模型的技术细节。

致谢

我要感谢Rik Huijzer和 Dina Boer 的校对。

参考文献

[1] Hinton “通过最小化对比散度训练专家产品神经计算 14.8, 1771–1800 (2002)。

[2] Smolensky “信息处理在动态系统中的应用:和谐理论的基础” 194–281 (1986).

[3] Hinton-Salakhutdinov, “使用神经网络减少数据的维度科学 313.5786, 504–507 (2006).

[4] Hinton-Osindero-Teh. “深度信念网络的快速学习算法神经计算 18.7, 1527–1554 (2006).

[5] Le Roux-Bengio, “限制玻尔兹曼机和深度信念网络的表示能力” 神经计算 20.6, 1631–1649 (2008).

[6] Tieleman, “使用似然梯度的近似训练限制玻尔兹曼机第 25 届国际机器学习大会论文集。2008 年。

学习通用模型用于异常检测

原文:towardsdatascience.com/learning-generalist-models-for-anomaly-detection-53d7a6a74474?source=collection_archive---------5-----------------------#2024-04-14

Guansong PangTowards Data Science Guansong Pang

·发布于Towards Data Science ·阅读时间:9 分钟·2024 年 4 月 14 日

--

通用异常检测(GAD)的目标是训练一个单一的检测模型,使其能够在不同应用领域的多样化数据集中泛化检测异常,而无需在目标数据上进行进一步训练。

该工作将于 CVPR 2024 发布[1]。

概述

最近的研究表明,大型预训练视觉-语言模型(VLMs)如 CLIP,在从不同数据集中检测工业缺陷时具有强大的泛化能力,但它们的方法过于依赖于手工制作的缺陷文本提示,这使得它们难以在其他应用中泛化,例如医疗影像异常或自然图像中的语义异常。

在本研究中,我们提出了通过少量正常图像作为样本提示,快速训练一个 GAD 模型,以便在多样化的数据集上进行异常检测。为此,我们引入了一种新方法,学习一种上下文残差学习模型来实现 GAD,称为InCTRL

它在一个辅助数据集上进行训练,通过查询图像和少量正常样本提示之间的残差的整体评估,来区分异常和正常样本。不论数据集如何,按照异常的定义,异常样本的残差通常会比正常样本更大,从而使 InCTRL 能够在不同领域之间进行泛化,无需进一步训练。

在九个 AD 数据集上进行全面实验,以建立一个 GAD 基准,涵盖了工业缺陷异常、医学异常和语义异常的检测,涉及一对多和多类设置,其中 InCTRL 表现最佳,显著优于现有的最先进竞争方法。代码可在github.com/mala-lab/InCTRL获取。

引言

异常检测(AD)是一个重要的计算机视觉任务,旨在检测那些与数据集中大多数样本显著偏离的样本,这一任务具有广泛的实际应用,例如工业检测、医学影像分析和科学发现等[2–3]。当前的 AD 范式集中于在每个目标数据集的训练数据上单独构建一个模型,例如无异常样本集的数据重建方法、一类分类和知识蒸馏方法。尽管这些方法在各种 AD 基准测试中表现出了显著的检测性能,但它们需要大量的训练数据以及每个数据集上需要熟练的检测模型训练。因此,在某些应用场景下,它们变得不可行,尤其是在由于数据隐私问题(例如,因使用这些数据训练模型而导致的机器反学习问题[3])或新应用部署中缺乏大规模训练数据等原因,无法对目标数据集进行训练的情况下。为了应对这些挑战,本研究探讨了通用异常检测(GAD)模型的问题,旨在训练一个单一的检测模型,该模型能够在不同应用领域的多样数据集中进行异常检测,而无需对目标数据进行任何训练。

大型视觉语言模型(VLMs),如 CLIP,已经在网页规模的图像-文本数据上进行预训练,近年来表现出卓越的泛化能力,能够在不同的数据集上实现准确的视觉识别,而无需对目标数据进行任何微调或适配。更重要的是,一些非常近期的研究(例如,WinCLIP [5])表明,这些 VLMs 也可以用来在不同的缺陷检测数据集上实现显著的泛化。然而,这些模型的一个重要限制是它们依赖于大量手工制作的、特定于缺陷的提示。这种依赖限制了它们的适用性,使得将其扩展到检测其他数据领域的异常变得具有挑战性,例如医学影像中的异常或在一对多或多类设置中的语义异常。

为了解决这个问题,我们提出训练一个 GAD 模型,该模型旨在利用来自任何目标数据集的少量正常图像作为样本提示,支持快速的 GAD,如图 1(顶部)所示。少量样本的设置源于一个事实,即在实际应用中,获取少量正常图像通常很容易。此外,这些少量样本并不用于模型训练/调优;它们仅作为样本提示,在推理过程中用于支持测试图像的异常评分。这个公式与当前的少量样本 AD 方法根本不同,后者使用这些目标样本及其扩展版本来训练检测模型,这可能导致目标数据集的过拟合,无法泛化到其他数据集,如图 1(底部)所示。

我们接着介绍了一种 GAD 方法,这是首个此类方法,通过基于 CLIP 学习一个in-context residual learning 模型,命名为InCTRL。它训练一个 GAD 模型,通过学习识别查询图像与一组来自辅助数据的少量正常图像之间的残差/差异,从而区分异常与正常样本。这些少量正常图像,亦即上下文样本提示,作为正常模式的原型。根据异常的定义,与这些正常模式的特征进行比较时,异常样本通常会产生比正常样本更大的残差,因此所学的上下文残差模型能够在不同领域的数据集上泛化,检测到各种类型的异常。为了更好地捕捉残差,InCTRL在图像和图块层面建模上下文残差,深入理解什么构成了异常。此外,我们的上下文残差学习还能够无缝地将正常/异常的文本提示引导的先验知识融入检测模型,为来自文本-图像对齐语义空间的检测提供额外的优势。

我们在九个 AD 数据集上进行了广泛的实验,以建立一个包含三种流行 AD 任务的 GAD 基准,包括工业缺陷异常检测、医学图像异常检测和语义异常检测,涵盖了单一对抗与多类设置。我们的结果表明,InCTRL显著超过了现有的最先进方法。

方法

我们的方法InCTRL旨在有效地建模查询图像与一组少量正常图像之间的上下文残差,利用 CLIP 的泛化能力,检测来自不同应用领域的异常残差。

CLIP 是一个视觉语言模型(VLM),由文本编码器和视觉编码器组成,通过在大规模的文本-图像数据上进行预训练,使得来自这些编码器的图像和文本表示得到良好的对齐。InCTRL通过在图像编码器中使用上下文残差学习,结合辅助数据进行优化,并通过文本编码器提供的文本提示引导的先验知识增强学习。

更具体地,如图 2 所示,我们首先模拟一个包含一个查询图像x和一组少量正常样本提示P'的上下文学习示例,这些图像和提示均随机从辅助数据中采样。然后通过视觉编码器,我们执行多层次的补丁级别和图像级别的残差学习,分别捕捉查询图像与少量正常样本提示之间的局部和全局差异。此外,我们的模型能够无缝地将来自文本编码器的正常和异常文本提示引导的先验知识融入到学习过程中,这些文本提示嵌入与查询图像之间的相似度用于指导学习。InCTRL的训练是优化附加在视觉编码器上的少量投影/适应层,使得训练数据中的异常样本相较于正常样本具有更大的异常分数,同时保持两个编码器中的原始参数冻结;在推理过程中,将测试图像与目标数据集中的少量正常图像提示和文本提示一起输入到我们改进的基于 CLIP 的 GAD 网络中,其输出为测试图像的异常分数。

实验结果

数据集与评估指标。 为了验证我们方法的有效性,我们在九个真实世界的异常检测数据集上进行了全面实验,包括五个工业缺陷检测数据集(MVTec AD, VisA, AITEX, ELPV, SDD)、两个医学影像数据集(BrainMRI, HeadCT),以及两个语义异常检测数据集:MNIST 和 CIFAR-10,采用一对多和多类协议。在一对多协议下,选择一个类作为正常类,其余类作为异常类;而在多类协议下,MNIST 中的偶数类和 CIFAR-10 中的与动物相关的类被视为正常类,其余类的图像被视为异常。

为了评估 GAD 性能,使用 MVTec AD 及其训练和测试集的组合作为辅助训练数据,在此上训练 GAD 模型,并随后在其他八个数据集的测试集上进行评估,无需进一步训练。在评估 MVTec AD 上的性能时,我们在 VisA 上训练模型。

针对目标数据的少量常规提示是从目标数据集的训练集中随机采样的,并且对于所有模型保持一致,以确保公平比较。我们评估了少量常规提示集为 K = 2, 4, 8 时的性能。报告的结果是基于三次独立实验的平均值,且每次实验使用不同的随机种子。

关于评估指标,我们使用两个流行的指标 AUROC(接收操作特征曲线下面积)和 AUPRC(精准率-召回率曲线下面积)来评估 AD 性能。

结果。 主要结果见表格 1 和表格 2。对于 11 个工业缺陷检测(AD)数据集,InCTRL在几乎所有的情况中,在三种少量样本设置下,均显著优于所有竞争模型,无论是在 AUROC 还是 AUPRC 方面。随着更多少量样本图像提示的加入,所有方法的表现普遍得到提升。InCTRL能够很好地利用增加的少量样本,并保持相对于竞争方法的优势。

消融研究。 我们考察了方法中三个关键组件对泛化能力的贡献:文本提示引导的特征(T)、补丁级残差(P)和图像级残差(I),以及它们的组合。结果见表格 3。实验结果表明,对于工业缺陷 AD 数据集,视觉残差特征在比文本提示特征更为重要,尤其是在像 ELPV、SDD 和 AITEX 这样的数据集上。在医学图像 AD 数据集上,视觉残差和文本知识对性能提升都有显著贡献,表现出互补关系。在语义 AD 数据集上,结果主要受到补丁级残差和/或文本提示特征的影响。重要的是,我们的三个组件通常是互相补充的,从而使得在各数据集上的检测泛化能力更强。

In-context 残差学习的重要性。 为了评估InCTRL中残差学习的重要性,我们在多层补丁级和图像级残差学习中尝试了两种替代操作:1)将残差操作替换为拼接操作,2)将残差操作替换为平均操作,其他InCTRL组件保持不变。如表格 3 所示,in-context 残差学习的泛化能力明显优于这两种替代方式,显著提升了该模型在 GAD 任务中的性能,且跨三个不同的领域表现良好。

结论

本文提出了一个 GAD 任务,用于评估异常检测方法在不同场景下识别异常的泛化能力,且无需在目标数据集上进行任何训练。这是首个专注于通用异常检测方法的研究,涵盖了工业缺陷、医学异常和语义异常。随后,我们提出了一种方法,称为InCTRL,在少样本设置下解决此问题。InCTRL通过整体上下文残差学习实现了卓越的 GAD 泛化能力。在九个异常检测数据集上进行了广泛实验,为上述三种流行的异常检测任务建立了 GAD 评估基准,在多个少样本设置下,InCTRL显著且持续地超越了现有最先进的竞争模型。

请查阅完整论文[1]以获取更多关于方法和实验的详细信息。代码可以在 github.com/mala-lab/InCTRL 公开获取。

参考文献

[1] Zhu, Jiawen, 和 Guansong Pang. “通过少样本提示的上下文残差学习实现通用异常检测。” arXiv 预印本 arXiv:2403.06495 (2024)。

[2] Pang, Guansong, 等. “深度学习在异常检测中的应用:综述。” ACM 计算机综述(CSUR) 54.2 (2021):1–38。

[3] Cao, Yunkang, 等. “视觉异常检测综述:挑战、方法和前景。” arXiv 预印本 arXiv:2401.16402 (2024)。

[4] Xu, Jie, 等. “机器遗忘:解决方案与挑战。” IEEE《计算智能新兴话题》期刊 (2024)。

[5] Jeong, Jongheon, 等. “Winclip: 零样本/少样本异常分类和分割。” IEEE/CVF 计算机视觉与模式识别会议论文集。2023。

学习排序 — 针对用户对的情境项目推荐

原文:towardsdatascience.com/learning-to-rank-contextual-item-recommendations-for-user-pairs-dc4f56e24d94?source=collection_archive---------6-----------------------#2024-03-26

训练一个机器学习推荐引擎,学习一群人共同的偏好

Jay FranckTowards Data Science Jay Franck

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

--

图片来自Lucrezia CarnelosUnsplash

本文教程适合…

  1. 任何对 DIY 推荐感兴趣的人

  2. 对基本的 PyTorch 排序模型感兴趣的工程师

  3. 咖啡迷

本文教程不适合…

  1. 想要将代码复制粘贴到生产系统中的人

  2. 想要一个 TensorFlow 模型的人

动机

想象一下,你正坐在沙发上,朋友或家人陪伴在侧。你打开了自己喜爱的游戏主机/流媒体服务/音乐应用,每一项都像是闪闪发光的可能性之宝,为你量身定制。但这些个性化的结果可能只是你独自一人的版本,并不能反映你在这群人中时的样子。

这个项目真的是从咖啡开始的。我迷恋自己烘焙来自 Sweet Maria's(无任何关联)的绿咖啡豆,因为它有各种美味的可能性。哥伦比亚? 爪哇豆? 肯尼亚圆粒? 每一种描述都比上一种更加诱人。即使是我自己作为个人,也很难做出选择。如果你是为家人或客人购买绿咖啡豆,情况会怎样呢?

我想创建一个“学习排名”(LTR)模型,可能解决这个咖啡难题。对于这个项目,我首先构建了一个简单的 TensorFlow Ranking 项目,来预测不同咖啡的用户对偶排名。我对 TFR 有一些经验,因此它似乎是一个自然的选择。

然而,我意识到我之前从未从零开始构建过一个排名模型!于是我着手构建了一个非常简陋的 PyTorch 排名模型,看看我能否快速搭建一个并在过程中学习一些东西。显然,这并不适用于生产系统,我在过程中做了很多捷径,但这的确是一次极好的教学体验。

数据

照片由 Pritesh Sudra 提供,来源于 Unsplash

我们的终极目标如下:

  • 开发一个学习用户对偶偏好的排名模型

  • 将此应用于预测 k 个项目的按列表排名

用户和项目特征组合中可能包含的信号是什么,以为该用户对提供一组推荐?

为了收集这些数据,我不得不和我的妻子一起进行痛苦的咖啡品鉴研究。然后我们各自为这些咖啡打分,满分为 10 分。目标值只是我们两人分数的总和(最大为 20 分)。模型的目标是学习排名我们都喜欢的咖啡,而不仅仅是某一对中的一个成员。我们将使用的上下文数据如下:

  • 一对用户的年龄

  • 用户 ID,将转换为嵌入向量

SweetMarias.com 提供了大量的项目数据:

  • 咖啡的来源

  • 加工和培养笔记

  • 品尝描述

  • 专业评分(100 分制)

因此,对于每个训练示例,我们将把用户数据作为上下文信息,每个项目的特征集将被连接在一起。

TensorFlow Ranking 模型通常在 ELWC 格式的数据上进行训练:ExampleListWithContext。你可以把它想象成一个字典,包含两个键:CONTEXT 和 EXAMPLES(列表)。每个 EXAMPLE 内部是一个字典,包含每个你想排名的项目的特征。

例如,假设我正在寻找一款新的咖啡来尝试,系统展示了一个包含 k=10 种咖啡品种的候选池。一个 ELWC 将包括上下文/用户信息,以及一个包含 10 个项目的列表,每个项目都有自己的特征集。

由于我不再使用 TensorFlow Ranking,我自己做了一个简陋的排名/列表构建部分。我随机挑选了 k 个项目样本,并为它们评分,然后将它们添加到一个列表中。我把最初尝试的咖啡分成了训练集,之后的示例则成为了一个小的验证集,用来评估模型。

特征直觉

在这个玩具示例中,我们拥有一个相当丰富的数据集。从上下文角度来看,我们明显知道用户的年龄,并能学习到他们各自的偏好嵌入。通过 LTR 模型内部的后续层,这些上下文特征可以进行比较和对比。例如,是否一对用户中,一个喜欢浓郁的水果味,而另一个则喜欢杯中清新的柑橘和水果味呢?

图片由Nathan Dumlao提供,来自Unsplash

对于商品特征,我们拥有丰富的描述性文本,包括每种咖啡的品尝笔记、产地等。稍后会详细介绍,但总体来说,我们可以捕捉这些描述的含义,并将这些描述与上下文(用户对)数据进行匹配。最后,我们还有一些数值特征,比如每种商品的专家品尝评分,这些(应该)与现实有一定的相似性。

数据预处理

自从我刚开始从事机器学习行业以来,文本嵌入技术发生了惊人的变化。那些我曾用来尝试捕捉词语或短语语义的 GLOVE 和 Word2Vec 模型早已不再使用。如果你访问huggingface.co/blog/mteb,你可以轻松比较最新最强的嵌入模型,用于各种目的。

为了简化和熟悉,我们将使用huggingface.co/BAAI/bge-base-en-v1.5的嵌入模型,帮助我们将文本特征投射到 LTR 模型能够理解的格式。具体来说,我们将使用这个模型来处理 Sweet Marias 提供的产品描述和产品名称。

我们还需要将所有用户和商品的 ID 值转换为嵌入空间。PyTorch 通过Embedding层很好地处理了这一点。

最后,我们对浮动特征进行简单的RobustScaler缩放。所有这些都可以在我们的 Torch 数据集类内部完成,然后将其输入到用于训练的 DataLoader 中。关键在于分离出不同的标识符,这些标识符将在 PyTorch 的forward()调用中传递。Offir Inbar的这篇文章真的是帮我节省了不少时间!

模型构建与训练

关于 Torch 训练,唯一有趣的事情是确保两个用户嵌入(每个评分者一个)和训练列表中的k种咖啡具有正确的嵌入和维度,以便通过我们的神经网络。经过一些调整,我终于得到了一些结果:

这个前向传播将每个训练示例推入一个包含所有特征的单一连接列表中。

由于数据点如此之少(仅有 16 种咖啡被评分),训练一个强大的神经网络模型可能会很困难。我通常会并排构建一个简单的 sklearn 模型,以便比较结果。我们真的学到了什么吗?

使用相同的数据准备技术,我构建了一个 LogisticRegression 多类分类器模型,然后将 .predict_proba() 的得分输出,用作我们的排序。我们的指标能告诉我们这两个模型的表现如何吗?

结果

对于指标,我选择追踪两个:

  1. Top(k=1)准确率

  2. NDCG

目标,当然,是正确排序这些咖啡。NDCG 在这里非常适用。不过,我怀疑 LogReg 模型在排序方面可能会遇到困难,所以我考虑可能也加入一个简单的准确率指标。有时你只想要一杯真正好的咖啡,而不需要排序!

在我没有进行任何显著的参数调整的情况下,两个模型的结果非常相似。SKLearn 在(极小的)验证集上的 NDCG 稍微差一些(0.9581 对比 0.950),但准确率相似。我相信通过对 PyTorch 模型和 LogReg 模型进行一些超参数调优,结果在如此少的数据情况下会非常相似。不过至少它们的大致结论一致!

未来工作

我有一批新的 16 磅咖啡开始进行排名,并且故意将一些不太为人知的品种加入其中。我希望能稍微整理一下仓库,让它看起来不那么像一个临时的黑客作品。另外,我还需要为未见过的咖啡添加一个预测函数,以便我能搞清楚下次该买什么!

需要注意的一点是,如果你正在为生产环境构建推荐系统,通常使用一个专门为排序构建的真实库是个不错的主意。TensorFlow Ranking、XGBoost、LambdaRank 等在业界被广泛接受,并且解决了许多痛点。

请查看这里的仓库,并告诉我如果你发现任何 bug!希望你能受到启发,训练你自己的用户对排序模型。

学会遗忘:为什么数据科学家和 AI 从业者应该理解机器遗忘

原文:towardsdatascience.com/learning-to-unlearn-why-data-scientists-and-ai-practitioners-should-understand-machine-unlearning-866af9e5d712?source=collection_archive---------8-----------------------#2024-08-22

图片由 Sue Winston 提供,来源于 Unsplash

探讨隐私与 AI 的交集,并通过使用 SISA 技术应用于卷积神经网络(CNNs)的 Python 示例,指导如何去除单个数据点对 AI 训练的影响。

Raul Vizcarra ChirinosTowards Data Science Raul Vizcarra Chirinos

·发表于 Towards Data Science ·阅读时间:20 分钟·2024 年 8 月 22 日

--

截至本文撰写之时,根据世界银行数据,全球超过 32%的人口(大约 80 亿人)年龄在二十岁以下。这意味着大约 26 亿人出生在社交媒体时代,而且几乎可以确定,他们几乎所有的生活都已经在线记录,由他们的父母、亲密圈子,最终可能是他们自己(取决于他们对社交媒体的依赖以及他们的网络)。如果再加上二十到五十岁之间的人群,我们就有另外 33 亿人,在某种程度上,他们的生活的一部分已经在线记录,涵盖不同的来源和格式(如图片、评论、视频等)。当然,我们可以根据超过五十岁的人群进行调整,或者考虑到并非每个人都有互联网接入或使用互联网根据世界银行 2021 年估算,至少有 35%的人无法接入或使用互联网),但我相信你明白我的意思。今天的数字世界中,确实有大量的我们生活的记录。

另一个高概率或也许是确定的我们可以再问一下 OpenAI 的 CTO🙄)是,这些数据中的大部分已经被用来训练今天部署的所有“最先进”模型,从大语言模型(LLM)到可以处理图像、视频或文本等信息的多模态 AI 模型。在这种背景下,当谈到数据、技术和隐私时,我们常常看到两方在寻找中间立场的过程中展开斗争。一方是每个人与技术之间的社会契约,我们愿意为获得技术带来的利益而交换部分数据权利。另一方面,是必须划定界限的问题,正如这一立场的支持者所说,“仅仅因为数据是可访问的,并不意味着它可以自由收集和使用”

在本文中,我们将探讨在讨论人工智能中的隐私时出现的一些挑战,包括对机器遗忘SISA 训练方法(分片、隔离、切片和聚合训练)的简要概述,SISA 是一种最近开发的机器遗忘框架,旨在帮助管理或减少个体数据点在 AI 训练中的影响,并解决与“被遗忘权”相关的合规性挑战。

照片由Tingey Injury Law Firm提供,来源于Unsplash

壁中低语的,将在屋顶上大声宣告。

历史上最早倡导隐私权的出版物之一是由两位美国律师塞缪尔·D·沃伦和路易斯·布兰代斯于 1890 年代发表的一篇文章。该文章标题为隐私权,旨在提高人们对未经授权的照片和早期报纸企业影响的认识,他们认为这些影响将八卦变成了一种商品,侵害了个人享受生活的权利,即被独立对待的权利

个人在身体和财产上的完全保护是与普通法同样古老的原则;但随着时间的推移,有时需要重新定义这种保护的确切性质和范围。….近期的发明和商业方法提醒我们,必须采取下一步措施来保护个人,并保障个体的权利,正如库利法官所称的“被独立对待的权利”(塞缪尔·D·沃伦,路易斯·布兰代斯,1890 年)。

自《隐私权》一书发布以来,时代已经发生了变化,但沃伦和路易斯·布兰代斯在一件事上并没有错;技术、政治、社会和经济的变革不断挑战现有或新兴的权利。对此,普通法应始终保持开放的态度,以应对社会的新需求,认识到社会的保护主要通过承认个人的权利来实现。

从那时起,隐私常常与传统的做法相联系,即保护我们关心和想要隐藏的东西,保持其不为公众所见,并控制其访问和使用。但同样的事实是,随着时间的推移,隐私的边界被颠覆性技术所挑战;摄影和视频设定了新的边界,最近则是数据的指数增长。然而,基于数据的技术不仅影响了数据合规的格局;它们还对我们的信仰和习惯产生了一些影响。社交媒体平台或超级应用就是一个例子,在这些平台上,我们愿意为了技术带来的好处而交换部分数据隐私。这意味着语境很重要,在某些情况下,分享我们的敏感信息更多依赖于像信任这样的价值观,而不一定是考虑隐私泄露。

“数据不仅仅是‘私密’或‘非私密’、‘敏感’或‘非敏感’的。语境很重要,社会的规范性价值观也是...”《高级 AI 助手的伦理》. Google DeepMind 2024

语境与隐私之间的关系是一个有趣的思维方向,被称为信息隐私模型。

“情境完整性” (Nissenbaum, 2004). 它指出,在发送者和接收者之间的每一次信息交换或流动中,都有社会规则来规范它。理解这些规则对确保信息交换得到适当监管至关重要。

图 01 来源:作者自创

一个清晰的例子可能是,例如,关于我孩子在学校表现的信息。如果一位老师将我孩子的成绩记录与其他家长或校外陌生人共享,我可能会认为这是隐私侵犯。然而,如果同样的老师将这些信息与其他教我孩子的老师共享,以便交流经验并改善我孩子的学校表现,我可能就不会那么担心,反而会依赖于信任、价值观和老师的良好判断。因此,在情境完整性方法下,隐私不再仅仅被看作是“独立生活的权利”这一僵化的状态。相反,重要的是信息流动应当得到适当的监管,考虑到其中的背景和治理规范,以界定其边界。隐私作为一项基本权利不应改变,但可以重新思考。

隐私的固有概念是否应该保持不变?还是我们应该首先理解支配信息流动的社会规则?

随着人工智能继续塑造未来,这一重新思考挑战着我们考虑是否需要适应现有的权利,或可能引入新的数字权利。

机器“遗忘”

无论你是将隐私视为一个僵化的概念,还是考虑情境完整性方法,我认为我们大多数人都会同意,我们所有人都应当享有公平处理个人数据的权利,且在需要时能获得我们的同意,并有权纠正或删除数据。

尽管《通用数据保护条例》(GDPR)促进了数据与隐私的共存,在监管框架中平衡隐私与人工智能仍然是一个不同的挑战。尽管我们可以从数据集删除或修改敏感数据,但在 AI 模型中这样做要复杂得多。AI 模型并非每天都重新训练,而且在大多数情况下,需要数月时间才能确保其可靠性。为了解决在 AI 模型中选择性地删除特定训练数据点(及其影响)而不显著牺牲模型性能的任务,像机器“遗忘”这样的技术应运而生,并正在进行研究,以寻找解决隐私问题的方案,遵守可能强制实施的法规,并保护用户删除或更正数据的法定权利。

与可以追溯到一百多年前的隐私政策研究相比,机器“遗忘”是一个相对较新的领域,最初的研究大约出现在 10 年前(Y. Cao 和 J. Yang, 2015)。

那么我们为什么要关注机器遗忘呢? 无论你是推动人工智能边界的 AI 研究员,还是在为终端用户优化 AI 解决方案的从业者,以下是将机器遗忘技术应用于机器学习流程的一些好理由:

· 被遗忘权(RTBF): 大型语言模型(LLMs)和最先进的基础模型以复杂且快速发展的方式处理数据。如同《通用数据保护条例》(GDPR)所见,用户请求删除权并将其纳入 AI 相关法规的制定,已只是时间问题。这将要求任何使用 AI 的公司调整流程以符合这些规定,并响应用户要求,从预训练模型中移除个人数据。

· 非零影响: 如今,差分隐私等框架的存在是为了通过引入噪音来确保对敏感数据集的一定隐私保护,从而隐藏单个数据点的贡献。然而,虽然差分隐私有助于减轻单个数据点的影响,但这种努力仍然是“非零的”。这意味着,目标数据点仍然有可能对模型产生某种影响。在需要完全删除数据点的情况下,可能需要采取不同的差分隐私方法。

· 性能优化: 众所周知,基础模型需要大量数据进行训练,这需要大量的时间和计算资源。从头开始重新训练完整模型以删除单个数据点可能是最有效的方式,以消除该数据点在模型中的任何影响,但这并不是最高效的做法 (模型需要频繁重新训练😨)。机器遗忘领域通过考虑时间和计算资源作为反向处理或消除特定数据点对模型参数影响的约束,来解决这个问题。

· 网络安全: 模型并不免受对手的攻击,这些攻击通过注入数据来操控模型行为,从而泄露用户的敏感信息。机器遗忘可以帮助去除有害数据点,保护用于训练模型的敏感信息。

在机器遗忘领域,我们可以找到两种思路:精确机器遗忘近似机器遗忘精确机器遗忘侧重于通过完全删除特定数据点来消除其影响 (就好像这些数据从未被引入模型一样),而近似机器遗忘旨在高效地减少训练模型中某些数据点的影响 (使模型的行为接近于如果这些数据点从未引入过模型的状态)。这两种方法都提供了多样化的技术来应对用户的删除权问题,同时考虑到模型性能下降、计算资源、时间消耗、存储资源、特定学习模型或数据结构等限制。

为了更好地理解该领域的最新研究工作,我推荐两篇有趣的阅读材料:机器去学习:解决方案与挑战(2024)学会去学习:机器去学习的见解(2023)。这两篇论文很好地回顾了近年来机器去学习领域科学家和研究人员的非凡工作。

SISA (分片、隔离、切片和聚合)

SISA 框架是“精确机器去学习”思想的一部分,旨在去除数据而无需对模型进行完全重新训练。该框架从这样一个前提出发:虽然从头开始重新训练,排除需要去学习的数据点,是与“被遗忘权”原则对齐的最直接方式(提供证明并确保不需要的数据已被移除),但它也认识到,对于使用大量数据集训练的复杂基础模型而言,这种方法可能会被视为一种天真的策略,因为这类模型训练需要高资源。因此,为了应对去学习过程的挑战,任何技术都应该满足以下要求:

  1. 易于理解(可理解性): 该技术应易于理解和实现。

  2. 准确性: 虽然某些准确性可能会丢失是合理的,但这一差距应该很小。

  3. 时间/计算效率: 它应比从头排除数据点所需的时间更少,并且所需的计算资源应类似于现有训练过程所用的资源。

  4. 易于验证(可证明的保证): 该技术应清楚地表明所请求的数据点已被去学习,而不会影响模型参数,并且证明可以轻松解释(即使是非专家也能理解)。

  5. 与模型无关: 它应适用于各种性质和复杂度的模型。

我们如何保证特定训练数据点的完全移除?我们如何验证去学习过程的成功?

SISA 框架(分片、隔离、切片和聚合)最初在 2019 年由 Bourtoule 等人提出,发表于论文 机器去学习,旨在提出一个替代解决方案,用于解决从机器学习模型中去除数据的问题,确保移除保证易于理解。该论文在引言部分易于阅读,但如果你不熟悉机器学习领域,可能会变得复杂。因此,我将尝试总结一些我认为有趣的技术特征,但如果你有时间,我强烈建议阅读这篇论文,它值得一读!(你还可以观看作者在 IEEE 安全与隐私研讨会上进行的 这篇视频演示 ,其中对论文的发现进行了有趣的展示)

SISA 训练方法包括多次复制模型,每个副本在数据集的不同子集上进行训练称为一个分片)。每个模型被称为“构成模型”。在每个分片内,数据进一步被划分为“切片”,并应用增量学习,相应地归档参数。每个构成模型在训练阶段主要与其分配的分片一起工作,同时在每个分片内使用切片来管理数据并支持增量学习。训练后,来自每个分片的子模型会被汇总,形成最终模型。在推理过程中,来自不同构成模型的预测结果会结合在一起,产生整体预测结果图 02说明了 SISA 训练方法的工作原理。

图 02 来源:作者基于 Bourtoule 等人论文(2019)自行创作

当需要去学习某些数据时,只有包含需要去学习数据点的分片中的构成模型会被重新训练(数据点会从特定分片中的某个切片中去学习)。

应用 SISA:针对图像识别的 CNN 模型去学习与再训练

为了理解如何应用 SISA,我将使用 Python 进行一个案例示例。最近,我使用 PyTorch、计算机视觉技术和卷积神经网络(CNN)构建了一个基本的设置,用于追踪冰球球员和球队,并收集一些基本的表现统计数据你可以在这里访问完整的文章

使用计算机视觉进行球员追踪

尽管秘鲁滑旱冰曲棍球协会(APHL)已同意将 40 秒的视频用于该项目,但我们设想一个 SISA 应用案例的场景:某个球员抱怨自己的图像被使用,并行使删除权,要求从分类每个球员所属队伍的 CNN 预训练模型中移除他的图像。这将要求我们从训练数据集中移除该图像,并重新训练整个模型。然而,通过应用 SISA 技术,我们只需处理包含这些图像的分片和切片,从而避免了从头开始重新训练整个模型,节省了时间。

原始 CNN 模型结构如下:

# ************CONVOLUTIONAL NEURAL NETWORK-THREE CLASSES DETECTION**************************
# REFEREE
# WHITE TEAM (white_away)
# YELLOW TEAM (yellow_home)

import os
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
import torchvision.transforms as transforms
import torchvision.datasets as datasets
from torch.utils.data import DataLoader
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
import matplotlib.pyplot as plt

#******************************Data transformation********************************************
# Training and Validation Datasets
data_dir = 'D:/PYTHON/teams_sample_dataset'

transform = transforms.Compose([
    transforms.Resize((150, 150)),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
])

# Load datasets
train_dataset = datasets.ImageFolder(os.path.join(data_dir, 'train'), transform=transform)
val_dataset = datasets.ImageFolder(os.path.join(data_dir, 'val'), transform=transform)

train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
val_loader = DataLoader(val_dataset, batch_size=32, shuffle=False)

#********************************CNN Model Architecture**************************************
class CNNModel(nn.Module):
    def __init__(self):
        super(CNNModel, self).__init__()
        self.conv1 = nn.Conv2d(3, 32, kernel_size=3, padding=1)
        self.pool = nn.MaxPool2d(kernel_size=2, stride=2, padding=0)
        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
        self.conv3 = nn.Conv2d(64, 128, kernel_size=3, padding=1)
        self.fc1 = nn.Linear(128 * 18 * 18, 512)
        self.dropout = nn.Dropout(0.5)
        self.fc2 = nn.Linear(512, 3)  #Three Classes

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = self.pool(F.relu(self.conv3(x)))
        x = x.view(-1, 128 * 18 * 18)
        x = F.relu(self.fc1(x))
        x = self.dropout(x)
        x = self.fc2(x)  
        return x

#********************************CNN TRAINING**********************************************

# Model-loss function-optimizer
model = CNNModel()
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

#*********************************Training*************************************************
num_epochs = 10
train_losses, val_losses = [], []

for epoch in range(num_epochs):
    model.train()
    running_loss = 0.0
    for inputs, labels in train_loader:
        optimizer.zero_grad()
        outputs = model(inputs)
        labels = labels.type(torch.LongTensor)  
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()
        running_loss += loss.item()

    train_losses.append(running_loss / len(train_loader))

    model.eval()
    val_loss = 0.0
    all_labels = []
    all_preds = []
    with torch.no_grad():
        for inputs, labels in val_loader:
            outputs = model(inputs)
            labels = labels.type(torch.LongTensor)  
            loss = criterion(outputs, labels)
            val_loss += loss.item()
            _, preds = torch.max(outputs, 1)  
            all_labels.extend(labels.tolist())
            all_preds.extend(preds.tolist())

#********************************METRICS & PERFORMANCE************************************

    val_losses.append(val_loss / len(val_loader))
    val_accuracy = accuracy_score(all_labels, all_preds)
    val_precision = precision_score(all_labels, all_preds, average='macro', zero_division=1)
    val_recall = recall_score(all_labels, all_preds, average='macro', zero_division=1)
    val_f1 = f1_score(all_labels, all_preds, average='macro', zero_division=1)

    print(f"Epoch [{epoch + 1}/{num_epochs}], "
          f"Loss: {train_losses[-1]:.4f}, "
          f"Val Loss: {val_losses[-1]:.4f}, "
          f"Val Acc: {val_accuracy:.2%}, "
          f"Val Precision: {val_precision:.4f}, "
          f"Val Recall: {val_recall:.4f}, "
          f"Val F1 Score: {val_f1:.4f}")

#*******************************SHOW METRICS & PERFORMANCE**********************************
plt.plot(train_losses, label='Train Loss')
plt.plot(val_losses, label='Validation Loss')
plt.legend()
plt.show()

# SAVE THE MODEL FOR THE GH_CV_track_teams CODE
torch.save(model.state_dict(), 'D:/PYTHON/hockey_team_classifier.pth')

如你所见,这是一个三层(conv1,conv2,conv3)的神经网络结构,使用 ReLU 作为激活函数,经过大约 90 张分类为三个类别的图像训练:裁判员、Team_Away(白色球衣的球员)和 Team_Home(黄色球衣的球员),并完成了 10 轮的完整周期。

考虑到这种初步方法,删除图像的请求将涉及从训练集和验证集中删除图像,并重新训练模型。虽然对于像我们这样的小数据集来说这可能很容易,但对于更大的数据集,比如当前大语言模型(LLM)所使用的数据集,这将是一次资源的巨大消耗。此外,反复执行此过程也可能成为一个限制。

现在,让我们假设在构建模型时,我们意识到用户有删除或更正的权利,并考虑应用 SISA 技术。这种方法将为模型做好准备,以应对未来可能需要将图像从训练数据集中永久删除的情况,以及 CNN 在学习过程中可能捕获的任何特征。第一步是将上述初始模型调整为包含 SISA 技术的四个步骤:分片(Sharding)、隔离(Isolating)、切片(Slicing)和聚合(Aggregation)。

步骤 01:分片和切片

在前面代码中指定的转换步骤之后,我们将通过将数据集划分为多个分片来开始应用 SISA。在代码中,您将看到这些分片是多样化的,并且被分割成大小相等的部分,以确保每个分片包含具有代表性的样本数量,并在我们要预测的不同类别之间保持平衡(在我们的案例中,我们正在预测三类)

 #******************************Sharding the dataset**************************

def shard_dataset(dataset, num_shards):
    indices = list(range(len(dataset)))
    np.random.shuffle(indices)
    shards = []
    shard_size = len(dataset) // num_shards
    for i in range(num_shards):
        shard_indices = indices[i * shard_size : (i + 1) * shard_size]
        shards.append(Subset(dataset, shard_indices))
    return shards

#******************************Overlapping Slices***************************
def create_overlapping_slices(shard, slice_size, overlap):
    indices = list(shard.indices)
    slices = []
    step = slice_size - overlap
    for start in range(0, len(indices) - slice_size + 1, step):
        slice_indices = indices[start:start + slice_size]
        slices.append(Subset(shard.dataset, slice_indices))
    return slices

您会注意到,在切片过程中,我没有像 SISA 技术建议的那样为每个分片分配独占的切片。相反,我们使用了重叠的切片。这意味着每个切片不仅仅由来自一个分片的数据点组成;一些数据点也会出现在下一个切片中。

那么,为什么我让切片重叠呢? 正如你可能已经猜到的那样,我们的数据集很小(大约 90 张图像),因此如果每个分片都使用独占的切片,将无法保证每个切片都有足够平衡的数据集来维持模型的预测能力。重叠切片 使得模型能够更好地利用可用数据并提高泛化能力。对于较大的数据集,非重叠切片可能更高效,因为它们需要的计算资源更少。最终,创建分片和切片需要考虑数据集的大小、计算资源以及维持模型预测能力的需求。

最后,在定义了函数之后,我们继续设置分片和切片过程的超参数:

 #**************************Applying Sharding and Slicing*******************

num_shards = 4  
slice_size = len(full_train_dataset) // num_shards // 2
overlap = slice_size // 2
shards = shard_dataset(full_train_dataset, num_shards)

#************************Overlapping slices for each shard*****************
all_slices = []
for shard in shards:
    slices = create_overlapping_slices(shard, slice_size, overlap)
    all_slices.extend(slices)

数据集被分为 4 个碎片,但我应该提到,最初我使用了 10 个碎片。这导致每个碎片只包含少量的样本,这没有正确代表整个数据集的类别分布,导致模型性能指标(准确率、精确度和 F1 分数)显著下降。由于我们处理的是一个小数据集,减少碎片数量到四个是一个明智的决定。最后,切片过程将每个碎片划分为两个具有 50% 重叠的切片,意味着每个切片中的一半图像与下一个切片重叠。

步骤 02:隔离特定数据点

在这一步骤中,我们继续隔离最终用户可能希望修正或从模型学习过程中删除的特定数据点。首先,我们定义一个函数,将指定的数据点从每个切片中移除。接下来,我们根据图像的文件名来确定图像的索引。然后,这些索引用来更新每个切片,移除包含这些数据点的部分。

 #**************************+*Isolate datapoints******************************
def isolate_data_for_unlearning(slice, data_points_to_remove):
    new_indices = [i for i in slice.indices if i not in data_points_to_remove]
    return Subset(slice.dataset, new_indices)

#*****Identify the indices of the images we want to rectify/erasure**********
def get_indices_to_remove(dataset, image_names_to_remove):
    indices_to_remove = [] #list is empty
    image_to_index = {img_path: idx for idx, (img_path, _) in enumerate(dataset.imgs)}
    for image_name in image_names_to_remove:
        if image_name in image_to_index:
            indices_to_remove.append(image_to_index[image_name])
    return indices_to_remove

#*************************Specify and remove images***************************
images_to_remove = []
indices_to_remove = get_indices_to_remove(full_train_dataset, images_to_remove)
updated_slices = [isolate_data_for_unlearning(slice, indices_to_remove) for slice in all_slices]

目前,列表为空(images_to_remove = []),因此此阶段没有删除任何图像,但当请求到达时,设置已准备好使用(稍后我们将在本文中看到一个例子)。

实施 SISA 技术的完整模型应该是这样的:

 import os
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
import torchvision.transforms as transforms
import torchvision.datasets as datasets
from torch.utils.data import DataLoader, Subset
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
import matplotlib.pyplot as plt

#******************************Data transformation********************************************
# Training and Validation Datasets
data_dir = 'D:/PYTHON/teams_sample_dataset'

transform = transforms.Compose([
    transforms.Resize((150, 150)),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
])

# Load data
full_train_dataset = datasets.ImageFolder(os.path.join(data_dir, 'train'), transform=transform)
val_dataset = datasets.ImageFolder(os.path.join(data_dir, 'val'), transform=transform)

#******************************Sharding the dataset**************************

def shard_dataset(dataset, num_shards):
    indices = list(range(len(dataset)))
    np.random.shuffle(indices)
    shards = []
    shard_size = len(dataset) // num_shards
    for i in range(num_shards):
        shard_indices = indices[i * shard_size : (i + 1) * shard_size]
        shards.append(Subset(dataset, shard_indices))
    return shards

#******************************Overlapping Slices***************************
def create_overlapping_slices(shard, slice_size, overlap):
    indices = list(shard.indices)
    slices = []
    step = slice_size - overlap
    for start in range(0, len(indices) - slice_size + 1, step):
        slice_indices = indices[start:start + slice_size]
        slices.append(Subset(shard.dataset, slice_indices))
    return slices

#**************************Applying Sharding and Slicing*******************

num_shards = 4  
slice_size = len(full_train_dataset) // num_shards // 2
overlap = slice_size // 2
shards = shard_dataset(full_train_dataset, num_shards)

#************************Overlapping slices for each shard*****************
all_slices = []
for shard in shards:
    slices = create_overlapping_slices(shard, slice_size, overlap)
    all_slices.extend(slices)

#**************************+*Isolate datapoints******************************
def isolate_data_for_unlearning(slice, data_points_to_remove):
    new_indices = [i for i in slice.indices if i not in data_points_to_remove]
    return Subset(slice.dataset, new_indices)

#*****Identify the indices of the images we want to rectify/erasure**********
def get_indices_to_remove(dataset, image_names_to_remove):
    indices_to_remove = []
    image_to_index = {img_path: idx for idx, (img_path, _) in enumerate(dataset.imgs)}
    for image_name in image_names_to_remove:
        if image_name in image_to_index:
            indices_to_remove.append(image_to_index[image_name])
    return indices_to_remove

#*************************Specify and remove images***************************
images_to_remove = []
indices_to_remove = get_indices_to_remove(full_train_dataset, images_to_remove)
updated_slices = [isolate_data_for_unlearning(slice, indices_to_remove) for slice in all_slices]

#********************************CNN Model Architecture**************************************

class CNNModel(nn.Module):
    def __init__(self):
        super(CNNModel, self).__init__()
        self.conv1 = nn.Conv2d(3, 32, kernel_size=3, padding=1)
        self.pool = nn.MaxPool2d(kernel_size=2, stride=2, padding=0)
        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
        self.conv3 = nn.Conv2d(64, 128, kernel_size=3, padding=1)
        self.fc1 = nn.Linear(128 * 18 * 18, 512)
        self.dropout = nn.Dropout(0.5)
        self.fc2 = nn.Linear(512, 3)  # Output three classes

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = self.pool(F.relu(self.conv3(x)))
        x = x.view(-1, 128 * 18 * 18)
        x = F.relu(self.fc1(x))
        x = self.dropout(x)
        x = self.fc2(x)
        return x

#********************************CNN TRAINING**********************************************

# Model-loss function-optimizer
model = CNNModel()
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

#*********************************Training*************************************************
num_epochs = 10
train_losses, val_losses = [], []

for epoch in range(num_epochs):
    model.train()
    running_loss = 0.0
    for slice in updated_slices:
        train_loader = DataLoader(slice, batch_size=32, shuffle=True)
        for inputs, labels in train_loader:
            optimizer.zero_grad()
            outputs = model(inputs)
            labels = labels.type(torch.LongTensor)
            loss = criterion(outputs, labels)
            loss.backward()
            optimizer.step()
            running_loss += loss.item()

    train_losses.append(running_loss / (len(updated_slices)))

    model.eval()
    val_loss = 0.0
    all_labels = []
    all_preds = []
    with torch.no_grad():
        val_loader = DataLoader(val_dataset, batch_size=32, shuffle=False)
        for inputs, labels in val_loader:
            outputs = model(inputs)
            labels = labels.type(torch.LongTensor)
            loss = criterion(outputs, labels)
            val_loss += loss.item()
            _, preds = torch.max(outputs, 1)
            all_labels.extend(labels.tolist())
            all_preds.extend(preds.tolist())

#********************************METRICS & PERFORMANCE************************************

    val_losses.append(val_loss / len(val_loader))
    val_accuracy = accuracy_score(all_labels, all_preds)
    val_precision = precision_score(all_labels, all_preds, average='macro', zero_division=1)
    val_recall = recall_score(all_labels, all_preds, average='macro', zero_division=1)
    val_f1 = f1_score(all_labels, all_preds, average='macro', zero_division=1)

    print(f"Epoch [{epoch + 1}/{num_epochs}], "
          f"Loss: {train_losses[-1]:.4f}, "
          f"Val Loss: {val_losses[-1]:.4f}, "
          f"Val Acc: {val_accuracy:.2%}, "
          f"Val Precision: {val_precision:.4f}, "
          f"Val Recall: {val_recall:.4f}, "
          f"Val F1 Score: {val_f1:.4f}")

#*******************************SHOW METRICS & PERFORMANCE**********************************
plt.plot(train_losses, label='Train Loss')
plt.plot(val_losses, label='Validation Loss')
plt.legend()
plt.show()

# SAVE THE MODEL
torch.save(model.state_dict(), 'hockey_team_classifier_SISA.pth') 

现在,让我们进入擦除场景。假设模型已经部署了几个月,一名冰球运动员请求从 CNN 模型的训练数据中删除他们的图像。假设在这个例子中,该运动员出现在训练和验证数据集中三张图像中:Away_image03.JPG, Away_image04.JPG 和 Away_image05.JPG。为了从训练过程中删除这些图像,只需在代码的 “指定并删除图像” 部分指定这些图像(如上所示)。只有包含这些图像的切片需要重新训练。

#*************************Specify and remove images***************************
images_to_remove = ["Away_image03.JPG", "Away_image04.JPG", "Away_image05.JPG"]
indices_to_remove = get_indices_to_remove(full_train_dataset, images_to_remove)
updated_slices = [isolate_data_for_unlearning(slice, indices_to_remove) for slice in all_slices]

最后,我想分享一些将 SISA 框架应用到我的模型中的关键经验:

  • 弱学习者和性能权衡: 由于每个构成模型都在小的子集(碎片和切片)上进行训练,人们可能会认为它们的准确度会低于在整个数据集上训练的单一模型,从而降低模型的泛化能力。令人惊讶的是,在我们的案例中,模型的性能显著提升,这可能是因为在处理一个小的、重叠的数据集时,导致了某种程度的过拟合。在涉及大数据集的使用案例中,需要考虑潜在的性能权衡

  • 适当的分片: 我最初使用了大量的分片,导致每个分片只有很少的样本,这对模型性能产生了负面影响。不要低估分片和切片过程的重要性。适当的分片有助于模型避免过拟合,并在验证集上更好地泛化。

我希望你觉得这个应用 SISA 技术进行机器“忘记”的项目有趣。你可以在这个GitHub 仓库访问完整的代码。

最后的思考

我的姐姐和我有一个固定的习惯,我们会交换社交媒体平台每天提醒我们五年、十年或十五年前发布的内容的图片。我们经常会对当时分享的内容或评论大笑(显然,因为我们大多数人在社交媒体刚出现时并不完全理解它)。随着时间的推移,我学会了更明智地使用我的社交媒体存在,欣赏社交媒体生态系统之外的周围环境,以及我们生活中某些方面应当拥有的隐私。但事实是,我和我的姐姐已经不再是十或十五年前的我们,尽管过去是我们现在身份的重要部分,但它并不定义我们(在数字世界中,并非所有事情都必须“刻在石板上”)。我们每个人都有权选择是否让这些数据停留在数字世界中,并决定是否用于定义我们的选择/偏好或他人的选择。

的确,AI 在使用与将要使用它的用户相似的数据进行训练时表现更好(《先进 AI 助手的伦理问题》,谷歌 DeepMind 2024)。然而,“隐私要求透明”。因此,使用机器学习并且涉及预训练敏感数据的公司,如何以及何时实施“被遗忘权”对于朝着我们都希望拥有的可信 AI 迈进至关重要。

感谢您的阅读! 一如既往,欢迎您的建议,并保持对话的进行。

我的数据分析师第一年学习总结

原文:towardsdatascience.com/learnings-from-my-first-year-of-being-a-data-analyst-f17d4e04a9cb?source=collection_archive---------7-----------------------#2024-10-26

处理统计数据、与人互动和在职场中提高生产力的洞察

Joseph BenTowards Data Science Joseph Ben

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

--

如果你不是 Medium 会员,点击👉🏽 这里 👈🏽可以免费阅读这篇文章。

去年八月,我加入了谷歌,成为一名数据分析学徒。这是我职业生涯的起点。一年过去了,我开始回顾这一年在工作和生活各个维度上学到的东西。我认为没有任何一个时期能像这一年一样让我经历如此迅速的蜕变。虽然充满挑战,但也非常有趣!

图片由作者提供

我将我的学习分为三个类别:数据科学、生产力和人际关系。

数据科学

  • 在现实世界的数据科学问题中,高准确度往往是因为数据集极度不平衡,而不是因为算法表现优秀。例如,在垃圾邮件分类中,数据集的负类与正类的比例可能是 1000:1,而这种不平衡会导致我们如果将所有点都分类为负类,准确率超过 99%。因此,选择合适的评估指标至关重要,在这种情况下,召回率是最重要的评估指标。较高的召回率表示正类数据被正确识别的程度…

最小二乘回归解析:带有代码示例的可视化指南(适合初学者)

原文:towardsdatascience.com/least-squares-regression-explained-a-visual-guide-with-code-examples-for-beginners-2e5ad011eae4?source=collection_archive---------1-----------------------#2024-11-05

回归算法

滑动穿越数据点以最小化平方差

Samy BaladramTowards Data Science Samy Baladram

·发表于 Towards Data Science ·阅读时间:11 分钟·2024 年 11 月 5 日

--

当人们开始学习数据分析时,他们通常从线性回归开始。这是有充分理由的——它是理解回归如何工作的最有用且直接的方式之一。线性回归最常见的方法被称为“最小二乘法”——它通过最小化预测值与实际值之间的平方差来寻找数据中的模式。最基本的类型是普通最小二乘法(OLS),它通过数据点找到最佳的直线拟合方式。

然而,有时,OLS 并不够——尤其是当你的数据有许多相关特征,可能导致结果不稳定时。这时,岭回归就派上用场了。岭回归与 OLS 做的工作相同,但它增加了一个特殊的控制项,帮助防止模型对任何单一特征过于敏感。

在这里,我们将快速了解两种最小二乘回归方法,探索这些算法如何平滑地穿过数据点,并在理论上看到它们的差异。

所有视觉内容:作者使用 Canva Pro 创建,已针对移动设备优化;在桌面上可能显示过大。

定义

线性回归是一种统计方法,使用线性方程预测数值。它通过拟合一条直线(或在多维情况下为平面)来模拟因变量与一个或多个自变量之间的关系。模型计算每个特征的系数,表示它们对结果的影响。要得到结果,你需要将数据的特征值输入到线性方程中以计算预测值。

📊 使用的数据集

为了说明我们的概念,我们将使用我们的标准数据集,它用于预测某一天来访的高尔夫球手人数。该数据集包括天气预报、温度、湿度和风力等变量。

列:‘Outlook’(一热编码为晴天、多云、雨天)、‘Temperature’(以华氏度为单位)、‘Humidity’(以百分比表示)、‘Wind’(是/否)以及‘Number of Players’(数值型,目标特征)

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split

# Create dataset
dataset_dict = {
    'Outlook': ['sunny', 'sunny', 'overcast', 'rain', 'rain', 'rain', 'overcast', 'sunny', 'sunny', 'rain', 'sunny', 'overcast', 'overcast', 'rain', 'sunny', 'overcast', 'rain', 'sunny', 'sunny', 'rain', 'overcast', 'rain', 'sunny', 'overcast', 'sunny', 'overcast', 'rain', 'overcast'],
    'Temp.': [85.0, 80.0, 83.0, 70.0, 68.0, 65.0, 64.0, 72.0, 69.0, 75.0, 75.0, 72.0, 81.0, 71.0, 81.0, 74.0, 76.0, 78.0, 82.0, 67.0, 85.0, 73.0, 88.0, 77.0, 79.0, 80.0, 66.0, 84.0],
    'Humid.': [85.0, 90.0, 78.0, 96.0, 80.0, 70.0, 65.0, 95.0, 70.0, 80.0, 70.0, 90.0, 75.0, 80.0, 88.0, 92.0, 85.0, 75.0, 92.0, 90.0, 85.0, 88.0, 65.0, 70.0, 60.0, 95.0, 70.0, 78.0],
    'Wind': [False, True, False, False, False, True, True, False, False, False, True, True, False, True, True, False, False, True, False, True, True, False, True, False, False, True, False, False],
    'Num_Players': [52, 39, 43, 37, 28, 19, 43, 47, 56, 33, 49, 23, 42, 13, 33, 29, 25, 51, 41, 14, 34, 29, 49, 36, 57, 21, 23, 41]
}

df = pd.DataFrame(dataset_dict)

# One-hot encode 'Outlook' column
df = pd.get_dummies(df, columns=['Outlook'],prefix='',prefix_sep='')

# Convert 'Wind' column to binary
df['Wind'] = df['Wind'].astype(int)

# Split data into features and target, then into training and test sets
X, y = df.drop(columns='Num_Players'), df['Num_Players']
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.5, shuffle=False)

虽然不是强制性的,但为了有效使用线性回归——包括岭回归——我们可以首先对数值特征进行标准化。

对“温度”和“湿度”应用标准化处理,而对“天气预报”和“风力”应用一热编码处理。

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.compose import ColumnTransformer

# Create dataset
data = {
    'Outlook': ['sunny', 'sunny', 'overcast', 'rain', 'rain', 'rain', 'overcast', 'sunny', 'sunny', 
                'rain', 'sunny', 'overcast', 'overcast', 'rain', 'sunny', 'overcast', 'rain', 'sunny', 
                'sunny', 'rain', 'overcast', 'rain', 'sunny', 'overcast', 'sunny', 'overcast', 'rain', 'overcast'],
    'Temperature': [85, 80, 83, 70, 68, 65, 64, 72, 69, 75, 75, 72, 81, 71, 81, 74, 76, 78, 82, 
                   67, 85, 73, 88, 77, 79, 80, 66, 84],
    'Humidity': [85, 90, 78, 96, 80, 70, 65, 95, 70, 80, 70, 90, 75, 80, 88, 92, 85, 75, 92, 
                 90, 85, 88, 65, 70, 60, 95, 70, 78],
    'Wind': [False, True, False, False, False, True, True, False, False, False, True, True, False, 
             True, True, False, False, True, False, True, True, False, True, False, False, True, False, False],
    'Num_Players': [52, 39, 43, 37, 28, 19, 43, 47, 56, 33, 49, 23, 42, 13, 33, 29, 25, 51, 41, 
                    14, 34, 29, 49, 36, 57, 21, 23, 41]
}

# Process data
df = pd.get_dummies(pd.DataFrame(data), columns=['Outlook'])
df['Wind'] = df['Wind'].astype(int)

# Split data
X, y = df.drop(columns='Num_Players'), df['Num_Players']
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.5, shuffle=False)

# Scale numerical features
numerical_cols = ['Temperature', 'Humidity']
ct = ColumnTransformer([('scaler', StandardScaler(), numerical_cols)], remainder='passthrough')

# Transform data
X_train_scaled = pd.DataFrame(
    ct.fit_transform(X_train),
    columns=numerical_cols + [col for col in X_train.columns if col not in numerical_cols],
    index=X_train.index
)

X_test_scaled = pd.DataFrame(
    ct.transform(X_test),
    columns=X_train_scaled.columns,
    index=X_test.index
)

主要机制

线性回归通过从数据中绘制一条直线(或超平面)来预测数字:

  1. 该模型通过使真实值与线性预测值之间的误差尽可能小来找到最佳拟合线。这被称为“最小二乘法”。

  2. 每个输入都会得到一个数字(系数/权重),表示它对最终结果的影响程度。还有一个起始数字(截距/偏置),当所有输入为零时使用该数字。

  3. 要预测一个新的答案,模型会将每个输入乘以其对应的数字,然后加总这些值,再加上起始数字。这样就得到了预测结果。

普通最小二乘法(OLS)回归

让我们从普通最小二乘法(OLS)开始——线性回归的基本方法。OLS 的目标是找到一条最佳拟合线,通过数据点。我们通过衡量预测与实际值之间的“误差”来实现这一点,然后找到一条使这些误差尽可能小的线。当我们说“误差”时,我们指的是每个点与线之间的垂直距离——换句话说,就是我们的预测与实际值之间的偏差。首先让我们看看二维情况发生了什么。

二维情况

在二维情况下,我们可以这样想象线性回归算法:

这是上述过程的解释:

1.我们从一个训练集开始,每一行包含:

· x :我们的输入特征(数字 1,2,3,1,2)

· y :我们的目标值(0,1,1,2,3)

2. 我们可以将这些点绘制在散点图上,我们希望找到一条最佳拟合这些点的直线 y = β₀ + βx

3. 对于任何给定的直线(任何 β₀ 和 β₁),我们可以通过以下方法来衡量它的好坏:

· 计算每个点到直线的垂直距离 (d₁, d₂, d₃, d₄, d₅)

· 这些距离是每个点的 |y — (β₀ + βx)|

4. 我们的优化目标是找到 β₀ 和 β₁,使得平方距离之和最小化:d₁² + d₂² + d₃² + d₄² + d₅²。在向量表示法中,这可以写作 ||y||²,其中 X = [1 x] 包含我们的输入数据(包含用于截距的 1),β = [ββ₁]ᵀ 包含我们的系数。

5. 最优解具有封闭形式:β = (XX)⁻¹Xᵀy。通过计算,我们得到 β₀ = -0.196(截距),β₁ = 0.761(斜率)。

这个向量表示法使得公式更为简洁,且展示了我们实际上是使用矩阵和向量进行运算,而不是单独的点。我们将在下文的多维情况中看到更多关于计算的细节。

在多维情况下(📊 数据集)

同样,最小二乘法(OLS)的目标是找到系数 (β),使得我们的预测与实际值之间的平方差最小化。数学上,我们将其表示为 最小化 ||y||²,其中 X 是我们的数据矩阵,y 包含我们的目标值。

训练过程遵循以下关键步骤:

训练步骤

1. 准备我们的数据矩阵 X。这涉及到添加一列 1 来考虑偏置/截距项 (β₀)。

2. 我们可以通过使用正规方程直接计算系数,而不是通过迭代搜索最佳系数:

β = (XX)⁻¹Xy

其中:

· β 是估计系数的向量,

· X 是数据集矩阵(包括一个用于截距的列),

· y 是标签,

· Xᵀ 表示矩阵 X 的转置,

· ⁻¹ 表示矩阵的逆。

让我们分解一下:

a. 我们将 Xᵀ(X 的转置)与 X 相乘,得到一个方阵

b. 我们计算这个矩阵的逆

c. 我们计算 Xy

d. 我们将 (XX)⁻¹ 与 Xy 相乘,以得到我们的系数

测试步骤

一旦我们得到了系数,进行预测就变得非常简单:我们只需将新的数据点与这些系数相乘,就能得到我们的预测。

在矩阵表示法中,对于一个新的数据点 x,预测 y 可以计算为

y = x × β = [1, x₁, x₂, …, xₚ] × [β₀, β₁, β₂, …, βₚ]ᵀ,

其中 β₀ 是截距,β₁ 到 βₚ 是每个特征的系数。

评估步骤

我们可以对所有数据点执行相同的过程。对于我们的数据集,以下是最终结果,并附有 RMSE 值。

岭回归

现在,让我们考虑岭回归,它在 OLS 的基础上解决了其一些局限性。岭回归的关键思想是,有时最优的 OLS 解涉及非常大的系数,这可能导致过拟合。

岭回归在目标函数中添加了惩罚项 (λ||β||²)。该项通过将系数的平方值添加到最小化目标中,来抑制大系数的出现。目标函数变为:

min ||yXβ||² + λ||β||²

λ(lambda)参数控制我们对大系数的惩罚程度。当 λ = 0 时,我们得到 OLS;随着 λ 增加,系数会向零收缩(但永远不会完全为零)。

训练步骤

  1. 就像 OLS 一样,准备我们的数据矩阵 X。这包括添加一列全为 1 的值来考虑截距项 (β₀)。

  2. 岭回归的训练过程与 OLS 类似,但有所修改。封闭形式的解变为:

    β = (XX+ λI)⁻¹Xy

其中:

· I是单位矩阵(第一个元素对应 β₀,某些实现中有时设置为 0,以排除截距项的正则化),

· λ是正则化值。

· Y是观察到的因变量值的向量。

· 其他符号与 OLS 部分定义相同。

让我们拆解一下:

a. 我们将 λI 加到 XX 上。λ的值可以是任何正数(比如 0.1)。

b. 我们计算该矩阵的逆。将 λI 加到 XX 后再进行求逆的好处是:

· 使矩阵可逆,即使 XX 不是(通过 OLS 解决一个关键的数值问题)

· 按 λ 比例缩小系数

c. 我们计算 (XX+ λI)⁻¹ 和 Xy 的乘积来获得系数

测试步骤

预测过程与 OLS 相同——将新的数据点与系数相乘。不同之处在于系数本身,通常它们比 OLS 的系数要小且更稳定。

评估步骤

我们可以对所有数据点执行相同的过程。对于我们的数据集,这里是带有 RMSE 的最终结果。

最后备注:选择 OLS 与岭回归

OLS 和岭回归的选择通常取决于你的数据:

  • 当你的数据表现良好,特征之间几乎没有多重共线性并且样本数足够时(相对于特征),使用 OLS

  • 当你有以下情况时,使用岭回归:

    • 特征多(相对于样本)

    • 特征中的多重共线性

    • OLS 中的过拟合迹象

使用岭回归时,你需要选择 λ。从一系列值开始(通常以对数间隔),选择能够提供最佳验证性能的那个值。

显然,默认值 λ = 1 为我们的数据集提供了最佳的 RMSE。

🌟 OLS 和岭回归代码总结

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.compose import ColumnTransformer
from sklearn.linear_model import LinearRegression
from sklearn.metrics import root_mean_squared_error
from sklearn.linear_model import Ridge

# Create dataset
data = {
    'Outlook': ['sunny', 'sunny', 'overcast', 'rain', 'rain', 'rain', 'overcast', 'sunny', 'sunny', 
                'rain', 'sunny', 'overcast', 'overcast', 'rain', 'sunny', 'overcast', 'rain', 'sunny', 
                'sunny', 'rain', 'overcast', 'rain', 'sunny', 'overcast', 'sunny', 'overcast', 'rain', 'overcast'],
    'Temperature': [85, 80, 83, 70, 68, 65, 64, 72, 69, 75, 75, 72, 81, 71, 81, 74, 76, 78, 82, 
                   67, 85, 73, 88, 77, 79, 80, 66, 84],
    'Humidity': [85, 90, 78, 96, 80, 70, 65, 95, 70, 80, 70, 90, 75, 80, 88, 92, 85, 75, 92, 
                 90, 85, 88, 65, 70, 60, 95, 70, 78],
    'Wind': [False, True, False, False, False, True, True, False, False, False, True, True, False, 
             True, True, False, False, True, False, True, True, False, True, False, False, True, False, False],
    'Num_Players': [52, 39, 43, 37, 28, 19, 43, 47, 56, 33, 49, 23, 42, 13, 33, 29, 25, 51, 41, 
                    14, 34, 29, 49, 36, 57, 21, 23, 41]
}

# Process data
df = pd.get_dummies(pd.DataFrame(data), columns=['Outlook'], prefix='', prefix_sep='', dtype=int)
df['Wind'] = df['Wind'].astype(int)
df = df[['sunny','overcast','rain','Temperature','Humidity','Wind','Num_Players']]

# Split data
X, y = df.drop(columns='Num_Players'), df['Num_Players']
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.5, shuffle=False)

# Scale numerical features
numerical_cols = ['Temperature', 'Humidity']
ct = ColumnTransformer([('scaler', StandardScaler(), numerical_cols)], remainder='passthrough')

# Transform data
X_train_scaled = pd.DataFrame(
    ct.fit_transform(X_train),
    columns=numerical_cols + [col for col in X_train.columns if col not in numerical_cols],
    index=X_train.index
)

X_test_scaled = pd.DataFrame(
    ct.transform(X_test),
    columns=X_train_scaled.columns,
    index=X_test.index
)

# Initialize and train the model
#model = LinearRegression() # Option 1: OLS Regression
model = Ridge(alpha=0.1)  # Option 2: Ridge Regression (alpha is the regularization strength, equivalent to λ)

# Fit the model
model.fit(X_train_scaled, y_train)

# Make predictions
y_pred = model.predict(X_test_scaled)

# Calculate and print RMSE
rmse = root_mean_squared_error(y_test, y_pred)
print(f"RMSE: {rmse:.4f}")

# Additional information about the model
print("\nModel Coefficients:")
print(f"Intercept    : {model.intercept_:.2f}")
for feature, coef in zip(X_train_scaled.columns, model.coef_):
    print(f"{feature:13}: {coef:.2f}")

进一步阅读

对于 OLS 线性回归岭回归 的详细解释及其在 scikit-learn 中的实现,读者可以参考它们的官方文档。文档提供了关于它们的使用和参数的全面信息。

技术环境

本文使用的是 Python 3.7 和 scikit-learn 1.5。虽然讨论的概念通常适用,但在不同版本中,具体的代码实现可能会有所不同。

关于插图

除非另有说明,所有图片均由作者创建,且融入了 Canva Pro 授权设计元素。

𝙎𝙚𝙚 𝙢𝙤𝙧𝙚 𝙍𝙚𝙜𝙧𝙚𝙨𝙨𝙞𝙤𝙣 𝘼𝙡𝙜𝙤𝙧𝙞𝙩𝙝𝙢𝙨 𝙝𝙚𝙧𝙚:

Samy Baladram

Samy Baladram

回归算法

查看列表5 篇故事一只戴着粉色帽子、绑着小辫子的卡通娃娃。这个“假人”娃娃,凭借其简单的设计和心形图案的衣服,形象地代表了机器学习中假回归器的概念。就像这个玩具般的形象是一个简化、静态的人物表现一样,假回归器是一个基本模型,用作更复杂分析的基线。

𝙔𝙤𝙪 𝙢𝙞𝙜𝙝𝙩 𝙖𝙡𝙨𝙤 𝙡𝙞𝙠𝙚:

Samy Baladram

Samy Baladram

分类算法

查看列表8 篇故事

没有基准?没有标准?没关系!一种敏捷聊天机器人开发的实验性方法

原文:towardsdatascience.com/lessons-from-agile-experimental-chatbot-development-73ea515ba762?source=collection_archive---------3-----------------------#2024-08-26

将基于大语言模型的产品推向生产环境的经验教训

Katherine MunroTowards Data Science Katherine Munro

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

--

今天的文章回顾了我最近的演讲,讲述了将基于大语言模型的产品推向生产环境的经验教训。你可以在这里查看该视频。

当你拿到一个已经在四种不同语言中为成千上万的客户提供服务的工作聊天机器人时,试图使用大语言模型来提供更好的体验会发生什么?这是个好问题。

大家都知道,评估和比较大语言模型(LLM)是非常棘手的。基准数据集很难获取,而诸如 BLEU 等度量标准也并不完美。但这些主要是学术上的问题:那么,行业中的数据团队在将大语言模型应用到生产项目时,如何解决这些问题呢?

在我作为对话式人工智能工程师的工作中,我正是在做这件事。这也让我在最近的一场数据科学会议上成为了焦点,进行了题为“No baseline? No benchmarks? No biggie!”的(乐观标题的)演讲。今天的文章是该演讲的总结,内容包括:

  • 评估一个不断发展的、基于大语言模型(LLM)驱动的概念验证(PoC)与一个正在运行的聊天机器人的挑战

  • 我们如何在从概念验证到生产的过程中,在不同阶段使用不同类型的测试

  • 不同测试类型的实际优缺点

从教授 SQL 给非技术团队的经验中得到的教训

原文:towardsdatascience.com/lessons-from-teaching-sql-to-non-technical-teams-7bd8fc9f8289?source=collection_archive---------2-----------------------#2024-03-08

从规模化方法到更量身定制的方法——以及我为什么认为远程辅导是未来趋势

Jordan GomesTowards Data Science Jordan Gomes

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

--

在我的职业生涯中,我曾多次参与并举办内部 SQL 培训。虽然这些培训从未是我工作的优先事项之一,但它们却是让我最有满足感的项目之一。当你看到有人开始轻松地运行查询,能够自己找到所需的信息,构建仪表板,甚至更广泛地说,开始对这项新学会的技能感到兴奋时;我不知道——那种感觉真好。

最近,我看到我的一位前“学生”的名字出现在一个共享小组里,提了一个相当复杂的 SQL 问题——我的反应和 Alfred 在《黑暗骑士崛起》里对 Bruce Wayne 点头时的反应一样(如果你不明白这个梗——这里有)。

本文的目标是带你走一遍我举办内部 SQL 培训的历程以及我的收获,培训的对象是整个非技术(或至少不擅长 SQL)的团队,希望你也能在你的组织中分享这些知识,并获得与我相同的喜悦。

图片来源:Campaign Creators via Unsplash

我最初举办这些培训的原因

一般来说,推动我举办这些培训的情况可以分为两个大类:

  1. 技能提升需求: 有时组织中的人因为缺乏 SQL 技能而达到了瓶颈。一个典型的症状是出现复杂的流程,涉及使用多个工具和电子表格来生成一个最终报告。虽然解决方案并不总是在 SQL 方面,但根据经验,如果你有一个耗时的多阶段流程,而你内心深处觉得一定有更好的方法来做你正在做的事情——很可能确实存在。

  2. 资源匮乏: 在资源匮乏的组织中,我发现识别具有“相关技能”的个人(即习惯于使用电子表格和数据的人)并提供技能提升机会,对组织和个人都非常有益。这样一来,不仅可以拓宽个人的视野,还能为企业创造更多的价值。

虽然你可能有许多理由去举办这样的培训(上面列出的清单并不详尽;也可以提出这个目标并不一定是互斥的),但明确你想要实现的目标是很重要的。根据你的目标,你执行培训的方式可能会有很大的不同。

早期的尝试,或者说我如何发现“千人一面”式培训的局限性

在我早期的尝试中(大约是 2015 年… 哎呀,时间过得真快!)我尝试了规模化的方法。通常的格式是典型的课堂形式:一个 X 周的项目,每周有 1 小时的课程(总是在同一时间、同一天),对任何有兴趣学习 SQL 的人开放,完全专注于 SQL:

  • 每周,小组成员都会学到新的内容,从 SQL 的“Hello World”(SELECT * FROM TABLE LIMIT 1)到如何使用多个 CTE 进行窗口函数并优化查询。

  • 每堂课之间,小组成员需要做某种家庭作业(即一些练习,以测试并巩固他们在课堂上学到的知识)

尽管有些人坚持到最后,但成功率(成功定义为有人在培训后仍继续使用新学的 SQL 技能)极低。每一节课,来的人越来越少。只有少数人在课外做提议的练习。从事实来看,这并不算成功。

但我从中得到了很多宝贵的经验:

  • 我喜欢指导别人: 这让我体验到辅导和教授新技能给他人带来的乐趣,最终也为我创办这个博客和参与其他让我觉得有价值的活动铺平了道路。

  • SQL 被认为“过于技术化”:许多人没有参加那些免费的培训,或者在遇到第一个障碍时就放弃了,仅仅因为他们认为 SQL 是为技术人员准备的,而他们自己并不认为自己是技术型人才。

  • 没有“保持”机制的培训注定会失败:这让我意识到建立一种保持学员的系统有多么重要。依赖学员的自律来完成这种培训是空想——在任何一个组织中,都有太多互相竞争的优先事项和原因,导致无法完成学习。因此,你要么需要找到那些对培训有强烈内在动机的学生(例如,你有一个明确的 SQL 学习目标),要么需要提供强大的外在动机(例如,他们的经理要求他们学习 SQL,以承担一些更具技术性的项目)。

  • 教授 SQL 只是其中的一部分:最后,也是更重要的一点——这让我意识到,不能只教 SQL。没有人是在真空中使用 SQL 的。SQL 的现实情况是:

  1. 在编写 SQL 代码之前,你需要在组织中找到正确的数据集(这在成熟的组织中可能很容易,但在不太成熟的组织中可能很复杂,甚至根本不存在)。

  2. 一旦你找到了数据集,你需要找到正确的字段进行查询,并确保这些字段包含你所需要的信息(这本身就是一门艺术)

  3. 从那里,你需要请求访问数据集,一旦获得访问权限,你需要在一个特定工具中编写 SQL 代码,该工具有特定的指南和功能。

  4. 在编写查询时,你需要关注计算成本,可能还需要在运行查询之前重新组织内容。

  5. 依此类推。如果你不教授这些元素,学生将很难使用 SQL。

所有这些学习为我程序的改进版铺平了道路——一种更具个性化的方式。

最近——向更个性化的方法转变

在经过几次改进后的迭代后,我反思了自己一路上的所有收获,并尝试了一种新方法:我放弃了规模,完全转向了相反的方向。与其每周进行 1 小时的全班授课,我开始每周与几位选定的个体进行简短的 1 对 1 会谈。

尽管该项目仍然向所有人开放,但现在有了一个选择过程。想要加入的人必须展示以下内容:

  • 清晰的学习 SQL 需求:未来的学生需要填写一份表格,解释他们为何想学习 SQL,并描述一个需要 SQL 的项目(例如:“我想自动化 X 报告,我想在 Y 上构建一个仪表板”)。如果他们被选中,这个项目将是他们在整个项目期间所做的工作。

  • 预先存在的相关技能:未来的学生需要展示我所称之为“相关技能”,即与 SQL 或数据分析所需技能相似的技能。

  • 能够(并愿意)在他们的日程安排中挤出足够的时间:作为他们申请该项目的一部分——学生必须与他们的经理验证他们的“学习”项目,并愿意在接下来的 X 周内将至少 X0%的时间投入到学习中。X0%可能看起来很多——但实际上它并不完全关于 X0%,而是传达了一个信息(插入小丑表情包)。这个项目将非常耗时,潜在的学生需要确保他们能够挤出成功所需的时间。

在培训本身——重点从 SQL 转移到做项目。培训的第一节课花费在将他们的项目拆分为里程碑上。第一个里程碑对每个人来说都差不多:找到一个资源(免费的或付费的,在线的或离线的——他们喜欢哪种都可以)来学习 SQL 的基础知识——并完成它。

我想承认这可能有些令人失望——你可能会期望一篇关于“教 SQL”的文章不会在“学习 SQL”部分这么干巴巴。我的普遍看法是,你可以在很短的时间内掌握 SQL 的关键概念,但要真正精通它需要几个月甚至几年,而你越早将它应用到实际问题中,就能越快达到扎实的水平。因此,程序的大部分时间花费在将其应用到现实问题上,而不是在获取 SQL 的基础知识(这些知识你可以通过互联网这一神奇的工具轻松获得)。

一旦上述第一步完成,我们就开始朝下一个里程碑努力。例如,对于那些想要构建仪表板的人——我们会将项目拆分为:

  1. 学习 SQL 的基础知识

  2. 寻找合适的数据集和查询逻辑(学习如何获取这些信息)

  3. 如有必要,构建一个数据库(与构建数据库相关的角色和责任)

  4. 将这个数据库连接到仪表板工具

  5. 设计仪表板

  6. 构建仪表板

从那时起,每个学生每周应该达到一个不同的里程碑。如果他们卡在某个地方,他们可以随时联系我,发送电子邮件或参加每周的办公时间,但一般来说,他们必须独立完成这些里程碑。

通过这个系统,我看到了相当高的成功率(成功的定义是某人在培训后继续使用他们新获得的 SQL 技能)。回顾这一点——我认为有几个原因:

  • 选择过程增加了摩擦:增加的选择过程确保只有最有动力的、拥有实际项目的人才能参与培训。

  • 里程碑系统是一个很好的强制性因素: 拥有目标是一个很好的开始,但如果没有考虑达成目标所需的步骤,更广泛地说,也没有考虑实现目标所需的工作,那么几乎不可能实现目标。拥有一个里程碑系统,设定明确的交付物和明确的截止日期,创造了一个责任感和反馈循环,这大大帮助学生成长。

  • 从一开始就设定正确的期望使一切变得更简单: 我相信,任何事情的成功很大一部分与心态和我们对工作的看法有关。从这个项目一开始,我就尽力设定正确的期望:(1) 这将是一个时间密集的过程 (2) 这将是一个充满挑战的过程 (3) 这将是一个漫长的过程

  • (4) 但我们会花时间,我们会一个一个地克服挑战,最终我们将会取得胜利

  • 教人们如何自学 SQL 与实际教授 SQL: 最后但同样重要的是——这一关键变化对项目产生了巨大影响。它让学员们熟悉了如何在网上寻找所需的关键信息,进行实验,并在过程中学习。这使他们变得更加独立,即使在项目结束后,也能继续成长。

到目前为止,上述方法是我实施过的最成功的之一。但它非常耗时,我仍然看到很多可以改进的地方。

朝着更加混合的方式前进

现在,主要的问题是:我们如何扩大上述项目的规模?如果我回顾我在这次培训中所扮演的角色,那主要是给出方向并让人们保持诚实:

  • 一开始: 我帮助学生们构建他们的项目并将其分解成多个里程碑

  • 在每个里程碑之前: 我会给他们一些关于如何应对每个障碍的最佳建议。如果他们卡住了,我会给他们提供解决问题的方向。

  • 在整个项目过程中: 我庆祝他们的成功,挑战他们,试图在他们遇到困难时保持他们的动力,但也让他们对按设定的时间表交付预定成果保持责任感。

我认为上述内容不能自动化——或者现在也许可以借助大型语言模型(LLMs),谁知道呢——但无论如何,你绝对可以标准化并优化它,也许可以以异步方式做很多工作,而不需要每周开会。这也是我希望在下一个迭代中尝试的——减少我在每个学生身上花费的时间,以便我能培养更多的学员。

作者注:我看到越来越多的健身影响者提供“远程辅导”,你可以通过电子邮件与教练沟通,发送你的训练视频,获得个性化的计划。也许可以做类似的数据分析辅导?

关于这个项目本身,我很希望能融入一些“社区”元素。特别是,我坚信费曼技巧——它是关于(以一种非常简化的方式)教授你所学到的东西。具体来说,我希望学生们开始记录他们的学习内容,并开始将其分享给新学生(有点像电影《善意的谎言》中的情节)。我在这里看到几个好处:

  • 它可以帮助扩展这个项目(可以把它看作一种“培训培训师”的方法),并让更多的人从知识中受益。

  • 这将帮助现在是教师的“学生”内化他们学到的关键概念,并识别他们理解中的空白。

  • 它将启动一个庞大的知识库,之后可以用于为那些非常有动力、但可能无法参与项目的个人提供更多自助式的学习方式。

一如既往,想法很便宜——执行才是你了解什么有效、什么无效的地方——我将很快尝试进行实验,并可能在未来的文章中报告结果。

总结

在过去的 8 年里,我尝试了不同的项目,目的是把同事和下属培养成 SQL 专家。我并不总是成功,但几年前我从一个广泛的项目转向更加量身定制的指导方式,取得了很大的成功,并学到了很多宝贵的经验。

我现在面临的真正挑战是方法的放大效应。我们如何简化并去除所有的废话,专注于为选定的个体创造尽可能多的价值,让他们有能力将自己在组织中产生的影响力扩大十倍?也许健身影响者已经有所发现……

希望你喜欢阅读这篇文章!你有什么想分享的建议吗?在评论区让大家知道吧!

如果你想阅读更多我的文章,这里有几篇你可能喜欢的其他文章

## 如何构建成功的仪表盘

来自一个曾经构建过一些不成功仪表盘的人的清单

towardsdatascience.com ## 建设分析成熟的组织(AMO)

一些简单的框架,用来确定你所在组织的分析需求,以及如何让它变得更加成熟……

towardsdatascience.com ## 区分优秀数据分析师的因素

还在寻找新一年的决心吗?这里有 6 个技能可以帮助你和你的团队变得极其高效。

towardsdatascience.com

PS: 这篇文章也发布在 Analytics Explained,这是一个我总结在不同分析角色中学到的知识(从新加坡初创企业到旧金山的大型科技公司),并回答读者关于分析、增长和职业的问题的通讯。

🚪🚪🐐 从蒙提霍尔问题中学习决策技巧

原文:towardsdatascience.com/lessons-in-decision-making-from-the-monty-hall-problem-a6032f4b1032?source=collection_archive---------1-----------------------#2024-10-24

三种直觉的探索:常识、贝叶斯和因果

Eyal KazinTowards Data Science Eyal Kazin

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

--

通过 Gemini Imagen 3 生成

蒙提霍尔问题是一个著名的脑筋急转弯,我们可以从中学习到重要的决策技巧,这些技巧对于数据科学家尤其有用。

如果你不熟悉这个问题,准备好感到困惑吧🤯。如果你已经知道这个问题,希望我能为你照亮一些你可能未曾考虑到的方面💡。

我介绍了这个问题,并通过三种类型的直觉来解决它:

  • 常识 — 本文的核心关注的是如何利用我们的常识来解决这个问题。我们将探索它为何失败 😕 ,以及我们如何直观地克服这个问题,让解决方案变得清晰明了 🤓。我们将通过使用视觉图示 🎨、定性论证和一些基本概率(保证不太深奥)来实现这一点。

  • 贝叶斯 — 我们将简要讨论信念传播的重要性。

  • 因果 — 我们将使用图模型来可视化在实际场景中使用蒙提霍尔问题所需的条件。

    🚨剧透警告 🚨 我还没有被说服认为有什么重要的结论,但这种思维过程非常有用。

我通过讨论更好的数据决策技巧总结了所学的经验。

通过实践领导:作为数据科学经理的经验教训,以及为何我决定回归个人贡献者角色

原文:towardsdatascience.com/lessons-learned-as-a-data-science-manager-and-why-im-moving-back-to-an-individual-contributor-role-65585b2e8dde?source=collection_archive---------0-----------------------#2024-07-13

我问自己三个问题,帮助我选择了职业路径

Dasha Herrmannova 博士Towards Data Science Dasha Herrmannova 博士

·发布于Towards Data Science ·阅读时间 9 分钟·2024 年 7 月 13 日

--

图片由罗伯特·鲁杰罗提供,来自Unsplash

随着你在数据科学领域经验的积累和职业的进展,某个时刻你可能会开始听到或者问自己这样一个问题:

你想转向管理岗位吗?

两年前,我的老板问了我这个问题。当时我是一名资深数据科学家。我成功地领导了多个项目,并在其他职责之外,还辅导了初级团队成员。由于我已经做了许多管理者的工作(或者我以为是这样),我认为转到管理岗位不会是一个大的变化。此外,我认为获得一个更“高级”的职称和薪资增长(大多是做相同的工作)是一个不错的交易,所以,尽管我从未计划过尝试成为一名管理者,但最终我同意从个人贡献者(资深数据科学家)转到领导岗位(数据科学经理)。

现在,在担任管理职务近两年后,我决定重新回到个人贡献者角色。稍后我会在本文中分享促使我做出这个决定的原因,但简而言之,我发现个人贡献者的角色更符合我的兴趣……

从开发开源软件中学到的经验

原文:towardsdatascience.com/lessons-learned-from-developing-an-open-source-software-9760eb18d1b2?source=collection_archive---------1-----------------------#2024-09-13

图片来自 Ideogram,由作者修改

以及它们给我作为数据科学家和机器学习工程师带来的意外优势

Siavash YasiniTowards Data Science Siavash Yasini

·发布于Towards Data Science ·17 分钟阅读·2024 年 9 月 13 日

--

介绍

开源在过去几十年中从根本上改变了软件开发的格局,尤其是在近年来,Python 无疑在所有编程语言中占据了主导地位(我相信某篇论文中有科学数学证明这一点)。作为一门极其易学的语言,Python 以其“开箱即用”的理念,带来了大量在数据领域中具有里程碑意义的开源软件包,涵盖了从科学计算和模拟,到数值分析和机器学习,再到如今的人工智能和聊天机器人开发等众多领域。

在我早期做博士研究生时,Python 并不像今天这样流行。我所在领域的大部分软件包和科研代码是用一种名为互动数据语言(IDL)的语言编写的。你可能会惊讶地发现,这门语言并不是免费的 —— 我们必须为此支付许可证费用。是的,你没看错,我们必须为编程付费

这段经历让我真正意识到 Python 不仅仅是一门编程语言,它还是一个平台,任何人都可以在这里贡献并创造出惊人的成果……

让光明降临!扩散模型与重光照的未来

原文:towardsdatascience.com/let-there-be-light-diffusion-models-and-the-future-of-relighting-03af12b8e86c?source=collection_archive---------3-----------------------#2024-11-04

了解最前沿的扩散模型如何在这篇关于场景编辑的深度博客中处理重光照、协调和阴影去除

Pulkit GeraTowards Data Science Pulkit Gera

·发布于 Towards Data Science ·阅读时间 15 分钟·2024 年 11 月 4 日

--

Brian Aitkenhead 拍摄,图片来源于 Unsplash

重光照是指在给定输入场景的情况下,在指定的目标光照条件下渲染场景的任务。这是计算机视觉和图形学中的一项重要任务。然而,它是一个病态问题,因为场景中物体的外观是由光源、几何形状和表面材质属性等因素之间复杂的相互作用所决定的。这些相互作用会产生歧义。例如,给定一张场景的照片,物体上的暗斑是由光照投射的阴影造成的,还是材质本身就是暗色的?区分这些因素是有效重光照的关键。

在这篇博客文章中,我们讨论了不同的论文如何通过扩散模型解决重光照问题。重光照涉及多个子问题,包括简单的光照调整、图像协调、阴影去除和内在分解。这些领域对于优化场景编辑至关重要,例如平衡合成图像中的颜色和阴影,或解耦材质与光照属性。我们将首先介绍重光照问题,并简要讨论扩散模型和控制网(ControlNets)。然后,我们将讨论在不同类型的场景中解决重光照问题的不同方法,从单一物体到肖像再到大规模场景。

解决重光照问题

目标是将场景分解为其基本组成部分,如几何形状、材质和光照交互,并对它们进行参数化建模。求解完成后,我们就可以根据自己的偏好进行修改。场景中一个点的外观可以通过渲染方程来描述,如下所示:

渲染方程来自source

大多数方法旨在求解渲染方程的每个单独组成部分。一旦求解完成,我们就可以进行重新照明和材质编辑。由于照明项 L 出现在方程的两边,这个方程无法通过解析方法求解,只能通过蒙特卡罗方法或基于近似的方法来求解。

另一种方法是数据驱动学习,在这种方法中,模型不显式地建模场景的属性,而是直接从数据中学习。例如,网络可以直接从数据中学习表面材质属性,而不是拟合一个参数化函数。数据驱动的方法已被证明比参数化方法更强大。然而,这些方法需要大量高质量的数据,特别是在照明和材质估计任务中,收集这些数据是非常困难的。

用于照明和材质估计的数据集非常稀缺,因为这些数据集需要昂贵且复杂的设备设置,如光照舞台,用以捕捉详细的光照交互。这些设置仅对少数组织可用,从而限制了用于训练和评估的数据的获取。目前没有公开的全身光照舞台真实数据集,这进一步凸显了这一挑战。

扩散模型

随着大量图像和视频数据在线可用,计算机视觉经历了重大变革。这推动了基础模型的发展,这些模型作为强大的通用模型,可以针对广泛的具体任务进行微调。扩散模型通过从独立样本中学习建模潜在的数据分布,逐渐逆转添加噪声的过程,从而生成真实的数据。通过利用其从学习到的分布中生成高质量样本的能力,扩散模型已成为解决各种生成任务的关键工具。

潜在扩散模型来自source

其中最突出的例子之一是Stable Diffusion(SD),它在大规模的 LAION-5B 数据集上进行了训练,该数据集包含 50 亿对图像和文本。它已经编码了大量关于视觉概念的通用知识,使其适合于针对特定任务的微调。它在训练过程中学习了诸如椅子有四条腿或识别汽车结构等基本关系和联想。这种内在的理解使 Stable Diffusion 能够生成高度连贯且逼真的图像,并可用于微调以预测其他模态。基于这一思想,问题出现了:我们能否利用预训练的 SD 来解决场景重光的问题?

那么我们如何微调 LDMs?一种简单的方法是对 LDMs 进行迁移学习。这种方法是冻结早期层(捕捉一般特征),并在特定任务上微调模型。虽然一些论文如Alchemist(用于材质迁移)已使用过这种方法,但它需要大量配对数据才能使模型很好地泛化。这个方法的另一个缺点是灾难性遗忘的风险,即模型会丧失在预训练过程中获得的知识。这将限制其在不同条件下的泛化能力。

来自来源的 ControlNet 图示

微调这些大模型的另一种方法是引入 ControlNet。在这种方法中,首先创建一个网络副本,并冻结原始网络的权重。在训练过程中,仅更新副本网络的权重,并将条件信号作为输入传递给副本网络。原始网络继续利用其预训练的知识。

尽管这增加了内存占用,但优势在于我们不会失去从大规模数据集训练中获得的泛化能力。它确保模型在学习当前任务所需的任务特定关系的同时,仍保持生成高质量输出的能力,适用于各种提示。

此外,它有助于模型学习控制输入与期望输出之间的稳健且有意义的关联。通过将控制网络与核心模型解耦,它避免了过拟合或灾难性遗忘的风险。同时,它所需的配对数据量显著减少。

虽然也有其他用于微调基础模型的技术——例如 LoRA(低秩适配)等——但我们将重点讨论两种方法:传统的迁移学习和 ControlNet。这些方法对于理解各种论文如何使用扩散模型处理基于图像的重光问题尤其重要。

DiLightNet

DiLightNet:基于扩散的图像生成的细粒度光照控制

介绍

本文提出了对输入图像重光照的细粒度控制。输入图像可以是生成的图像,也可以是作为输入给定的图像。进一步地,它还可以根据文本提示改变物体的材质。目标是对光照效果进行细粒度的控制。

方法

方法图来自于source

给定一个输入图像,应用以下预处理步骤:

  1. 使用现成的 SOTA 模型估计背景和深度图。

  2. 通过三角剖分深度图提取网格

  3. 生成 4 种不同的辐射线索图像。辐射线索图像是通过为提取的网格分配不同的材质,并在目标光照下渲染它们来创建的。这些辐射线索图像作为编码光照效果(如高光、阴影和全局光照)的基础。

ControlNet 的输入来自于source

一旦这些图像生成,它们将训练一个 ControlNet 模块。输入图像和掩膜会通过一个编码解码网络,该网络输出一个 12 通道的特征图。然后将该特征图与辐射线索图像按通道进行拼接。因此,在训练过程中,带噪的目标图像会以这个自定义的 12 通道图像作为条件信号进行去噪。

此外,提供了一个外观种子,以确保在不同的光照条件下保持一致的外观。如果没有它,网络会呈现出不同的光物质相互作用的解读。此外,可以通过文本提供更多线索来改变外观,例如添加“塑料/光亮金属”来改变生成图像的材质。

实现

数据集使用来自 Objaverse 的 25K 个合成物体进行策划。每个物体从 4 个独特视角进行渲染,并在 12 种不同的光照条件下进行渲染,这些条件包括点光源、多个点光源、环境光图和区域光。为了训练,辐射线索图像是在 Blender 中渲染的。

ControlNet 模块使用稳定扩散 v2.1 作为基础预训练模型进行微调。训练大约用了 30 小时,使用了 8 块 NVIDIA V100 GPU。训练数据是在 Blender 中以 512x512 分辨率渲染的。

结果

DiLightNet 的结果来自于source

该图展示了作为参考的临时图像和物体在重新光照下的目标光照。

DiLightNet 的结果来自于source

该图展示了如何使用文本提示来改变物体的材质。

DiLightNet 的结果来自于source

该图展示了 AI 生成的临时图像的更多结果,这些图像随后在不同的输入环境光照条件下进行了渲染。

DiLightNet 结果来自于source

该图展示了当外观种子未固定时,网络为解决光照交互问题所提出的不同解决方案。

局限性

由于在合成物体上进行训练,该方法在处理真实图像时效果不佳,而在 AI 生成的临时图像上表现更好。此外,材料的光照交互可能无法完全按照提示的意图进行。由于它依赖于深度图生成辐射线索,因此可能无法获得令人满意的结果。最后,生成旋转光源的视频可能无法产生一致的结果。

神经光照

神经光照:通过扩散重新照明任何物体

介绍

本文提出了一种端到端的 2D 光照扩散模型。该模型从具有物理基础材料和 HDR 环境图的合成数据集中学习物理先验。它还可以用于重新照亮多个视角,并用于创建场景的 3D 表示。

方法

神经光照方法图来自于source

给定一张图像和一个目标 HDR 环境图,目标是学习一个能够合成重新照明版本的图像的模型,这里是一个单一的物体。这是通过采用预训练的Zero-1-to-3模型实现的。Zero-1-to-3 是一个扩散模型,它以视角方向为条件来渲染输入图像的新视图。他们丢弃了新视图合成的部分。为了结合光照条件,他们将输入图像和环境图的编码与去噪潜在变量拼接在一起。

输入的 HDR 环境图 E 被拆分为两个组件:E_l,一个色调映射的 LDR 表示,用于捕捉低强度区域的光照细节,以及 E_h,一个对数归一化的图,用于保留跨整个光谱的信息。这两者共同为网络提供了一个平衡的能量谱表示,确保在重新照明时不会因过高的亮度而导致生成的输出看起来被冲淡。

此外,输入图像的 CLIP 嵌入也作为输入传递。因此,模型的输入包括输入图像、LDR 图像、标准化的 HDR 图像和图像的 CLIP 嵌入,这些都为去噪网络提供条件。该网络随后作为先验用于进一步的 3D 物体重新照明。

实现

该模型在一个自定义的 Relit Objaverse 数据集上进行训练,该数据集包含 90K 个物体。对于每个物体,提供了 204 张在不同光照条件和视角下渲染的图像。总的来说,数据集包含了 1840 万张分辨率为 512x512 的图像。

该模型是从 Zero-1-to-3 的检查点进行微调的,且仅微调了去噪网络。输入环境图被下采样至 256x256 分辨率。该模型在 8 个 A6000 GPU 上训练了 5 天。进一步的下游任务,如基于文本的重光照和物体插入也可以实现。

结果

他们展示了与不同背景的对比,以及与其他工作如 DilightNet 和IC-Light的对比。

Neural Gaffer 的结果来源于source

该图展示了他们的方法与另一种基于 ControlNet 的方法 IC-Light 的重光照结果对比。他们的方法能够与旋转的环境图一致地产生光照和颜色。

Neural Gaffer 的结果来源于source

该图展示了他们的方法与另一种基于 ControlNet 的方法 DiLightnet 的重光照结果对比。他们的方法能够产生镜面高光和准确的颜色。

局限性

一个主要的局限性是它只生成低分辨率的图像(256x256)。此外,它仅适用于物体,并且在人物重光照方面表现较差。

重光照协调

Relightful Harmonization: Lighting-aware Portrait Background Replacement

简介

图像协调是将前景主体的颜色和光照特征与背景对齐的过程,使其成为一个合理的合成。该研究提出了一种基于扩散的方法来解决这一任务。

方法

方法图来自source

给定一个输入合成图像、alpha 掩码和目标背景,目标是预测一张重光照的人物图像。通过训练一个 ControlNet 来预测协调后的图像输出,从而实现这一目标。

在第一阶段,我们训练了一个背景控制网络模型,该模型以合成图像和目标背景为输入,输出重光照的人物图像。在训练过程中,去噪网络将嘈杂的目标图像与合成图像拼接在一起,并预测噪声。背景作为条件通过控制网络提供。由于背景图像本身是 LDR,它们没有提供足够的信号用于重光照。

在第二阶段,训练了一个环境映射控制网络模型。HDR 环境映射提供了更多的信号用于重光照,这带来了更好的效果。然而在测试时,用户仅提供 LDR 背景。因此,为了弥合这一差距,两个控制网络模型彼此对齐。

最后,使用环境图控制网络模型生成更多数据,然后微调背景控制网络模型,以生成更具照片真实感的结果。

实现

用于训练的数据集包含 40 万对图像样本,这些样本是通过 100 个光阶段(lightstage)精心策划的。在第三阶段,生成了额外的 20 万个合成样本,用于光线真实感的微调。

该模型是从 InstructPix2Pix 检查点进行微调的,模型在 8 个 A100 GPU 上进行训练,分辨率为 512x512。

结果

Relightful Harmonization 的结果来自于 source

该图展示了该方法如何中和输入图像中显著的阴影,这些阴影通常很难去除。左侧为输入图像,右侧为重新光照后的图像。

Relightful Harmonization 的结果来自于 source

Relightful Harmonization 的结果来自于 source

图示展示了在真实世界测试对象上的结果。与其他方法相比,他们的方法能够去除阴影,并使得合成更加可信。

局限性

尽管该方法能够可信地重光照目标,但在保持身份一致性方面表现不佳,尤其在保持衣物或头发的颜色方面有困难。此外,它可能在去除阴影方面存在问题。同时,它没有估计反射率(albedo),而反射率对于复杂的光照交互至关重要。

多光照合成

基于扩散方法的辐射场重光照与多光照合成

介绍

本研究提出了一种 2D 重光照扩散模型,该模型进一步用于重光照场景的辐射场。首先训练一个 ControlNet 模型,预测场景在新光照方向下的表现。然后,使用该模型生成更多数据,最终用于拟合一个可重光照的辐射场。在本节中,我们讨论了 2D 重光照模型。

方法

给定一组图像 X_i 和对应的深度图 D(通过现成方法计算得到),以及光照方向 l_i,目标是预测场景在光照方向 l_j 下的表现。在训练过程中,去噪网络的输入是随机光照下的 X_i 图像,深度图 D 与含噪的目标图像 X_j 拼接在一起。光照方向通过 4 阶 SH 编码,并通过 ControlNet 模型进行条件化。

方法图来自于 source

尽管这产生了相当不错的结果,但也存在一些显著的问题。它无法保持颜色,导致对比度丧失。此外,它还会产生扭曲的边缘。为了解决这个问题,他们通过将预测图像与输入图像进行颜色匹配来弥补颜色差异。这是通过将图像转换为 LAB 空间并进行通道归一化来完成的。然后计算真实值和去噪输出之间的损失。为了保持边缘,解码器在图像修复任务上进行了预训练,这有助于保持边缘。然后,使用这个网络来创建在新光照方向下的相应场景,进一步用来创建可重光照的辐射场表示。

实现

来自source的 2D 重光照模块推理图

该方法是基于多光照数据集开发的。该数据集包含了 1000 个真实的室内场景,捕捉了 25 个光照方向下的图像。图像还包括一个漫反射球和一个金属球,这对于获取世界坐标系中的光照方向非常有用。此外,还使用 Blender 渲染了更多的场景。网络训练使用了分辨率为 1536x1024 的图像,训练包括了 1015 个室内场景中的 18 个非正面朝向的光照方向。

ControlNet 模块使用 Stable Diffusion v2.1 模型作为基础进行训练。它在多个 A6000 GPU 上训练了 150K 次迭代。

结果

来自source的结果图

这里的漫反射球展示了测试时的光照方向。可以看到,该方法可以渲染出合理的重光照结果。

来自source的结果图

这张图展示了随着光照方向的变化,镜面高光和阴影的移动,正如水壶上闪亮的高光所示。

来自source的结果图

这张图将结果与其他可重光照的辐射场方法进行了比较。与其他方法相比,他们的方法显著地更好地保留了颜色和对比度。

局限性

该方法没有强制物理准确性,可能会产生不正确的阴影。此外,它也难以完全去除阴影,且无法完全准确地去除阴影。此外,对于光照变化不大的分布外场景,它的表现还算合理。

LightIt

LightIt: 照明建模与扩散模型的控制

简介

本文提出了一种单视角阴影估计方法,用于生成配对图像及其对应的直射光阴影。然后可以利用这个阴影来引导场景生成并对场景进行重新照明。他们将该问题作为内在分解问题处理,其中场景可以分解为反射率和阴影。我们将在此讨论重新照明的组件。

方法

方法图来自于 source

给定一张输入图像、对应的表面法线、文本条件和目标直射光阴影图像,他们生成了一张重新照明的风格化图像。这是通过训练一个 ControlNet 模块实现的。

在训练过程中,带噪声的目标图像与文本条件一起传递到去噪网络。法线图和目标直射光阴影图像被串联后传入残差控制编码器。特征图随后被用来作为网络的条件输入。此外,它还通过残差控制解码器进行重构,以规范化训练过程。

实现

数据集图来自于 source

数据集包含了户外 Laval 数据集,其中包含了真实世界的户外 HDR 全景图像。通过这些图像,裁剪出了 250 张 512x512 的图像,并应用了各种相机效果。数据集包含了 51250 个 LDR 图像样本和文本提示,以及估算的法线图和阴影图。法线图是通过使用现成的估算器从深度图中估算得出的。

ControlNet 模块是基于稳定扩散 v1.5 进行微调的。该网络训练了两个周期。其他训练细节未公开。

结果

结果图来自于 source

该图展示了生成的图像在自定义风格化文本提示下,光照与目标阴影保持一致。这与其他仅关注照片级真实感的论文有所不同。

结果图来自于 source

该图展示了在不同光照条件下的身份保持效果。

结果图来自于 source

该图展示了在不同风格和场景下,随着光照条件变化的结果。

结果图来自于 source

该图将重新照明与另一种方法进行了比较。利用扩散先验有助于提高泛化能力并解决阴影歧义问题。

局限性

由于该方法假设定向光照,它支持沿任意方向追踪光线。它需要阴影线索来生成图像,而这些线索并不容易获取。此外,他们的方法不适用于肖像和室内场景。

主要收获

我们讨论了一个非详尽的文献列表,这些文献利用二维扩散模型进行重光照。我们探索了多种方式来条件化扩散模型进行重光照,从辐射线索、直接阴影图像、光照方向到环境图。大多数这些方法仅在合成数据集上展示结果,并且难以很好地推广到不同分布的数据集。每天都有更多的论文发表,基础模型也在不断改进。最近发布了IC-Light2,这是一个基于 Flux 模型的 ControlNet 模型。它的发展方向非常值得关注,因为维持身份一致性是一个棘手的问题。

参考文献:

  1. GitHub — lllyasviel/IC-Light: 更多重光照!

  2. IllumiNeRF — 无需反向渲染的三维重光照

  3. Neural Gaffer

  4. DiLightNet:基于扩散图像生成的精细化光照控制

  5. Relightful Harmonization

  6. 一种基于扩散方法的辐射场重光照使用多重照明合成

  7. 扩散模型是如何工作的:从零开始的数学讲解 | AI Summer

  8. 成像与视觉领域的扩散模型教程

  9. 从零开始实现扩散模型的 PyTorch 教程

  10. 扩散模型 — 现场编码教程

  11. 扩散模型 | 论文解读 | 数学原理讲解

  12. 我如何理解扩散模型 由黄家宾教授讲解

  13. 去噪扩散概率模型 | DDPM 讲解 对扩散模型的数学直觉理解

posted @ 2025-01-09 18:54  绝不原创的飞龙  阅读(838)  评论(0)    收藏  举报