docs-merge-17
TowardsDataScience 2024 中文翻译(十八)
剧透警告:RAG 的魔力并不来自 AI
为什么检索而非生成使 RAG 系统变得神奇
·发表于Towards Data Science ·阅读时长 8 分钟·2024 年 11 月 17 日
--
快速 POC
大多数允许用户借助对话式 AI 探索数据的快速概念验证(POC)会让你惊叹不已。当你突然间能够与文档、数据或代码库进行对话时,那感觉就像是纯粹的魔法。
这些 POC 在小数据集和有限数量的文档中表现出色。然而,就像几乎所有东西一样,当你将它投入生产时,很快就会遇到规模化的问题。当你深入调查并检查 AI 给出的答案时,你会发现:
-
你的代理没有回复完整的信息。它漏掉了一些重要的数据。
-
你的代理并不能可靠地给出相同的答案。
-
你的代理无法告诉你它是如何以及从哪里获得这些信息的,这使得答案的实用性大大降低。
事实证明,RAG 中的真正魔力并不发生在生成式 AI 步骤中,而是在检索和组合的过程中。一旦你深入了解,就会明白为什么…
** RAG = 检索增强生成 —* Wikipedia 定义的 RAG
RAG 过程 — 插图
那么,一个启用 RAG 的 AI 代理是如何回答问题的呢?
简要回顾一下简单的 RAG 过程如何工作:
-
一切都从查询开始。用户提出问题,或者某个系统正在尝试回答一个问题。
-
搜索是通过查询来进行的。通常你会嵌入查询并进行相似度搜索,但你也可以进行经典的弹性搜索,或两者的结合,或者直接查找信息。
-
搜索结果是一组文档(或文档片段,但我们暂且称之为文档)。
-
文档和查询的要点被组合成易于阅读的上下文,以便 AI 能够使用它
-
AI 解读问题和文档,并生成答案
-
理想情况下,这个答案会被事实核对,以查看 AI 是否是基于文档得出的答案,或者是否适合目标受众
魔法在哪里?
一个不为人知的秘密是,RAG 流程的本质是你必须在 AI 做任何事情之前就向它提供答案,这样它才能给你你想要的回复。
换句话说:
-
AI 所做的工作(步骤 5)是应用判断,并正确地表达答案
-
工程师所做的工作(步骤 3 和 4)是找到答案并将其组合成 AI 能够理解的形式
哪个更重要?答案当然是,视情况而定,因为如果判断是关键要素,那么 AI 模型就完成了所有的“魔法”。但是对于无数的业务用例来说,找到并正确组合构成答案的各个部分,才是更重要的部分。
如果你想要一个合适的 RAG 流程,典型的工程问题有哪些?
运行 RAG 流程时需要解决的第一个问题是数据摄取、拆分、分块和文档解析问题。我在之前的文章中写过一些相关内容,但在这里不再提及。暂时假设你已经解决了数据摄取问题,拥有一个漂亮的向量存储或搜索索引。
典型挑战:
-
重复性 — 即使是最简单的生产系统也常常会有重复文档。当系统规模很大,用户或租户众多,连接多个数据源,或处理版本控制等情况时,重复现象会更加严重。
-
近似重复 — 文档中大部分内容相同,但有一些小的变化。近似重复有两种类型:
— 有意义的 — 例如一个小的修正,或者一个小的添加,例如更新了日期字段
— 无意义的 — 例如:小的标点符号、语法或空格差异,或者仅仅是由时间差或输入处理引起的差异
-
数量 — 某些查询可能涉及到一个非常大的相关响应数据集
-
数据的新鲜度与质量 — 哪些响应数据片段具有最高质量的内容供 AI 使用,而哪些片段则是从时间(新鲜度)角度最相关的?
-
数据多样性 — 如何确保搜索结果多样性,以确保 AI 获得充分的信息?
-
查询措辞与模糊性 — 触发 RAG 流程的提示,可能没有以一种能产生最佳结果的方式表达,或者可能本身就存在模糊性
-
响应个性化 — 查询可能需要根据提问者不同而给出不同的回答
这个列表还可以继续,但你明白了大概意思。
旁注:无限上下文窗口不解决这个问题吗?
简短的回答:不。
使用极大上下文窗口的成本和性能影响不容小觑(你可能会将每次查询的成本提高 10 倍甚至 100 倍),更不用提用户/系统的任何后续交互。
然而,抛开这些不谈,想象一下以下情形。
我们让安妮进入一个房间,桌子上有一张纸。纸上写着:病人乔:复杂性脚部骨折。现在我们问安妮,病人是否有脚部骨折?她的回答是:“是的,他有”。
现在我们给安妮提供了乔的 100 页病历记录。她的回答变成了:“嗯,这取决于你指的是哪个时间点,他曾经有过……”
现在我们给安妮提供了关于诊所所有病人的成千上万页文档……
你很快会注意到,我们如何定义问题(或者在我们这里是提示)变得非常重要。上下文窗口越大,查询所需的细节越多。
此外,上下文窗口越大, 可能的答案范围也越广。这可能是件好事,但实际上,这种方法容易引发懒惰的工程行为,如果处理不当,可能会削弱应用程序的能力。
推荐方法
当你将一个 RAG 系统从 POC(概念验证)扩展到生产环境时,以下是如何通过具体解决方案应对典型数据挑战。
重复
在多来源系统中,重复是不可避免的。通过使用指纹识别(哈希内容)、文档 ID 或语义哈希,你可以在数据摄取时识别出完全重复的内容并防止冗余。然而,整合重复项之间的元数据也是有价值的;这可以让用户知道某些内容出现在多个来源中,这可能会增加可信度或突出数据集中的重复内容。
# Fingerprinting for deduplication
def fingerprint(doc_content):
return hashlib.md5(doc_content.encode()).hexdigest()
# Store fingerprints and filter duplicates, while consolidating metadata
fingerprints = {}
unique_docs = []
for doc in docs:
fp = fingerprint(doc['content'])
if fp not in fingerprints:
fingerprints[fp] = [doc]
unique_docs.append(doc)
else:
fingerprints[fp].append(doc) # Consolidate sources
接近重复
接近重复的文档(相似但不完全相同)通常包含重要的更新或小的补充内容。考虑到像状态更新这样的微小变化可能包含关键信息,因此在过滤接近重复项时,新鲜度变得至关重要。一种实用方法是先使用余弦相似度进行初步检测,然后在每个接近重复的文档组中保留最新版本,同时标记任何有意义的更新。
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.cluster import DBSCAN
import numpy as np
# Cluster embeddings with DBSCAN to find near duplicates
clustering = DBSCAN(eps=0.1, min_samples=2, metric="cosine").fit(doc_embeddings)
# Organize documents by cluster label
clustered_docs = {}
for idx, label in enumerate(clustering.labels_):
if label == -1:
continue
if label not in clustered_docs:
clustered_docs[label] = []
clustered_docs[label].append(docs[idx])
# Filter clusters to retain only the freshest document in each cluster
filtered_docs = []
for cluster_docs in clustered_docs.values():
# Choose the document with the most recent timestamp or highest relevance
freshest_doc = max(cluster_docs, key=lambda d: d['timestamp'])
filtered_docs.append(freshest_doc)
数据量
当查询返回大量相关文档时,如何有效处理至关重要。一种方法是采用分层策略:
-
主题提取:预处理文档以提取特定的主题或摘要。
-
Top-k 过滤:在合成后,基于相关性评分过滤摘要内容。
-
相关性评分:使用相似性度量(例如 BM25 或余弦相似度)在检索前优先排序最相关的文档。
这种方法通过检索经过合成的信息,减轻了工作负担,使得 AI 处理起来更为高效。其他策略可能包括按主题批量处理文档或预先对摘要进行分组,以进一步简化检索过程。
数据的新鲜度与质量
在快速变化的数据集中,平衡质量与新鲜度至关重要。虽然有多种评分方法,但这里有一种通用策略:
-
复合评分:通过源可靠性、内容深度和用户参与等因素计算质量得分。
-
时效加权:通过时间戳权重调整得分,以强调新鲜度。
-
按阈值过滤:只有满足质量和时效阈值的文档才会进行检索。
其他策略可能涉及仅对高质量来源评分或对较旧文档应用衰减因子。
数据多样性
确保检索中的数据来源多样化有助于创建平衡的响应。通过来源(例如,不同的数据库、作者或内容类型)对文档进行分组,并从每个来源选择最佳片段是一种有效方法。其他方法包括按独特视角评分或应用多样性约束,以避免过度依赖任何单一文档或视角。
# Ensure variety by grouping and selecting top snippets per source
from itertools import groupby
k = 3 # Number of top snippets per source
docs = sorted(docs, key=lambda d: d['source'])
grouped_docs = {key: list(group)[:k] for key, group in groupby(docs, key=lambda d: d['source'])}
diverse_docs = [doc for docs in grouped_docs.values() for doc in docs]
查询措辞和模糊性
模糊查询可能导致次优的检索结果。直接使用用户的原始提示通常不是检索所需结果的最佳方式。例如,可能在聊天的早期有一个相关的信息交换,或者用户粘贴了一大段文本并提出了相关问题。
为确保使用精确的查询,一种方法是确保提供给模型的 RAG 工具要求其将问题重新表述为更详细的搜索查询,类似于人们为 Google 精心设计搜索查询的方式。这种方法提高了用户意图和 RAG 检索过程之间的对齐度。下面的措辞虽然不理想,但大致传达了意思:
tools = [{
"name": "search_our_database",
"description": "Search our internal company database for relevant documents",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "A search query, like you would for a google search, in sentence form. Take care to provide any important nuance to the question."
}
},
"required": ["query"]
}
}]
响应个性化
为了提供量身定制的响应,直接将用户特定的上下文整合到 RAG 上下文组成中。通过在最终上下文中添加用户特定的层,允许 AI 考虑个人偏好、权限或历史记录,而不改变核心检索过程。
通过解决这些数据挑战,你的 RAG 系统可以从一个有说服力的概念验证(POC)进化为一个可靠的生产级解决方案。最终,RAG 的有效性更多依赖于精心的工程设计,而不是 AI 模型本身。虽然 AI 可以生成流畅的答案,但真正的“魔力”在于我们如何检索和构建信息。因此,下次当你对 AI 系统的对话能力感到印象深刻时,请记住,这很可能是得益于背后精心设计的检索过程。
我希望这篇文章能为你提供一些关于 RAG 过程的见解,并解释为什么在与数据交谈时,你体验到的“魔力”不一定来自 AI 模型,而更多依赖于你的检索过程设计。
请留下评论,分享你的想法。
识别地震数据中的时空模式
使用基于密度的聚类和生存分析来估算地震发生的时间
·发表于 Towards Data Science ·阅读时间 9 分钟·2024 年 1 月 10 日
--
照片来自 Eliška Motisová 在 Unsplash
介绍
虽然我们对地震发生的地点和原因有相当好的理解,但理解地震发生的时间却非常具有挑战性。在本文中,我们将使用历史地震数据,并结合聚类分析和生存分析来回答以下问题:
“地震事件是如何在空间上分布的?”
“如果发生地震,接下来一小时内发生另一场地震的概率是多少?”
“地震事件之间的时间是否存在区域性差异?”
获取数据
我们将使用来自 USGS[1] 的数据,该数据记录了地震事件(地震数据属于公共领域,由美国地质调查局提供)。他们提供了一个非常方便的 API,使我们可以在 Python 中直接获取地震数据:
import http.client
import pandas as pd
import json
url = '/fdsnws/event/1/query'
query_params = {
'format': 'geojson',
'starttime': "2020-01-01",
'limit': '10000',
'minmagnitude': 3,
'maxlatitude': '47.009499',
'minlatitude': '32.5295236',
'maxlongitude': '-114.1307816',
'minlongitude': '-124.482003',
}
full_url = f'https://earthquake.usgs.gov{url}?{"&".join(f"{key}={value}" for key, value in query_params.items())}'
print('defined params...')
conn = http.client.HTTPSConnection('earthquake.usgs.gov')
conn.request('GET', full_url)
response = conn.getresponse()
在我们的 API 请求中,有一些参数:
-
limit — 我们希望的最大地震事件数量
-
starttime — 最早的地震事件应发生的时间
-
minmagnitude — 地震事件的最小震级
-
maxlatitude — 最大纬度
-
minlatitude — 最小纬度
-
maxlongitude — 最大经度
-
minlongitude — 最小经度
我们将最小震级设为 3,因为通常在地面上可以感觉到的地震就是这个震级,并且提供经纬度坐标,围绕加利福尼亚州制作一个边界框。加利福尼亚是著名的圣安德烈亚斯断层的所在地,因此将有足够的地震事件供我们分析。
为了处理 GeoJSON 响应,我们使用以下代码:
if response.status == 200:
print('Got a response.')
data = response.read()
print('made the GET request...')
data = data.decode('utf-8')
json_data = json.loads(data)
features = json_data['features']
df = pd.json_normalize(features)
if df.empty:
print('No earthquakes recorded.')
else:
df[['Longitude', 'Latitude', 'Depth']] = df['geometry.coordinates'].apply(lambda x: pd.Series(x))
df['datetime'] = df['properties.time'].apply(lambda x : datetime.datetime.fromtimestamp(x / 1000))
df['datetime'] = df['datetime'].astype(str)
df.sort_values(by=['datetime'], inplace=True)
else:
print(f"Error: {response.status}")
这将返回一个 Pandas DataFrame,其中每一行是一个地震事件,列描述了事件属性(即,经度、纬度、震级、ID 等)。
对于那些有地理空间思维的人,你会认出 API 调用的坐标表示的是一个地震的边界框(即,在一个框定区域内查找地震),然而我们只关心加利福尼亚的地震。因此,我们需要过滤掉所有没有发生在加利福尼亚的地震(例如,发生在内华达州和俄勒冈州的地震)。为此,我们将使用方便的 OSMNX 包来获取加利福尼亚边界的多边形:
import osmnx
import geopandas as gpd
place = "California, USA"
gdf = osmnx.geocode_to_gdf(place)
# Get the target geometry
gdf = gdf[["geometry", "bbox_north", "bbox_south", "bbox_east", "bbox_west"]]
接下来,我们将使用 Shapely 将地震坐标转换为点几何体,然后执行空间连接以过滤掉非加利福尼亚的地震:
from shapely.geometry import Point
# Convert to a GeoDataFrame with Point geometry
geometry = [Point(xy) for xy in zip(df['Longitude'], df['Latitude'])]
earthquake_gdf = gpd.GeoDataFrame(df, geometry=geometry, crs='EPSG:4326')
# Filter to keep only points within the California bounding box
points_within_california = gpd.sjoin(earthquake_gdf, gdf, how='inner', predicate='within')
# Extract latitude, longitude etc.
df = points_within_california[['id', 'Latitude', 'Longitude', 'datetime', 'properties.mag']]
现在让我们看看第一个加利福尼亚地震的地图,按震级进行颜色编码:
加利福尼亚地震地图。数据来自 USGS。图片由作者提供。
很好,我们现在有了加利福尼亚地震的地图!
地震的空间聚类
从之前地图上绘制的地震分布情况来看,你可以看到事件呈现出线性 SE-NW 方向排列,你可以看到大约 2 到 3 个明显的线性形状地震分组。这是有道理的,因为:
-
地震是由于沿着断层的运动引起的,断层是线性特征(即,地壳中的裂缝)。
-
地震事件的排列方向与圣安德烈亚斯断层带的方向一致。
我们现在的目标是根据地震事件的位置进行聚类,以便我们可以生成与区域断层相关的空间聚类。
对于聚类,我们将使用HDBSCAN(基于密度的层次空间聚类与噪声),这是一种基于密度的聚类算法,适用于某些类型的数据集,如那些具有不规则形状的聚类和不同聚类密度的数据集。这与我们的数据集相关,因为地震事件可能发生在不规则形状的聚类中(即,沿着不同方向的断层)并且具有不同的密度(即,一些断层区比其他的更活跃)。HDBSCAN 也对离群点具有鲁棒性,在本案例中,离群点可能是远离断层区发生的地震。
通过一些实验,以下参数产生了不错的结果(这有一点试错过程,但受我们之前关于地震如何聚类的假设指导):
# Fit HDBSCAN
clusterer = HDBSCAN(min_cluster_size=200, metric='haversine', min_samples=20, cluster_selection_epsilon=0.05)
result_df['cluster_label_hdbscan'] = clusterer.fit_predict(data_scaled)
# Find the number of unique cluster labels
num_clusters = result_df['cluster_label_hdbscan'].nunique()
# Create a list of colors
colors = ['lightgray', 'red', 'blue', 'green'][:num_clusters]
# Create a Folium map centered at the mean coordinates
map_center = [result_df['Latitude'].mean(), result_df['Longitude'].mean()]
mymap = folium.Map(location=map_center, zoom_start=6)
# Add markers for each data point with cluster color
for _, row in result_df.iterrows():
cluster_color = colors[row['cluster_label_hdbscan'] + 1] # Map cluster label to color
folium.CircleMarker(
location=[row['Latitude'], row['Longitude']],
radius=2,
color=cluster_color,
fill=True,
fill_color=cluster_color,
fill_opacity=0.7,
popup=f'Cluster: {row["cluster_label_hdbscan"]}'
).add_to(mymap)
mymap
按照 HDBSCAN 聚类结果颜色编码的地震位置。图片由作者提供。
太好了,我们现在已经使用 HDBSCAN 将地震事件聚类为三个区域,这与我们先前的区域知识一致。请注意,一些地震被认为是异常值,并用灰色表示。
检查地震之间的时间间隔
由于我们有每次地震的日期和时间,我们可以计算每次事件之间的时间(即两次地震之间的时间间隔),这在下一节的生存分析中是必需的。我们已经根据‘datetime’列对数据进行了排序,因此它们按时间顺序排列,现在我们需要计算时间差:
from datetime import datetime
# Convert 'time' column to datetime objects
df['time'] = pd.to_datetime(df['datetime'])
# Sort dataframe by time
df.sort_values(by=['time'], inplace=True)
# Calculate time elapsed between consecutive events
df['time_elapsed'] = df['time'].diff().shift(-1)
由于最后一次地震(即最近发生的那次地震)没有时间差,我们可以计算与我们假设的当前日期的时间差:
# Assuming present time is '2024-01-08 16:06:00.000'
present_time = pd.to_datetime('2024-01-08 16:06:00.000')
# For the last event of each group, replace NaN with time between event and present time
df['time_elapsed'].fillna(present_time - df['time'], inplace=True)
最后,我们将添加一个新列,指示一次地震是否紧随另一场地震。尽管这看起来有些多余,因为只有最后一次地震会受到影响,但它突出了使用生存分析进行此类任务时的一个有趣点(我们将在后续讨论):
# Label events based on whether they happened or not
df['event_happened'] = df['time_elapsed'].apply(lambda x: 1 if pd.Timedelta(days=0) > 0 else 0)
下面是地震事件间经过时间的直方图:
地震间隔时间的直方图。图片由作者提供。
我们可以看到,大多数地震发生在彼此短时间内(大约每 2.5 小时发生一次),而在较长时间间隔之间发生的地震要少得多(即该数据集中地震之间的最大时间间隔为 350 小时)。
地震时间间隔的生存分析
我们有了地震簇群,以及地震之间的时间间隔,因此现在我们使用生存分析来告诉我们地震发生的概率。我们的目标将是:
-
使用生存分析创建曲线,展示在发生地震之后,某个时间点发生地震的概率。
-
比较我们在已聚类的地震区域中的概率曲线,看看区域性地震活动是否存在相似性/差异性。
什么是生存分析?
有很多写得很好的文章[2,3,4]介绍了这一内容,但简而言之,生存分析是一种统计技术,用于分析事件发生的时间数据,通常用于生物医学或观察性研究中。它着重于估计和建模事件发生的时间,诸如死亡、故障或其他特定结果,提供了关于事件发生概率随时间变化的洞见,以及协变量对事件发生的影响。
在我们的案例中,已经发生了地震,我们想知道未来发生另一场地震的概率。 注意:我们假设地震事件是独立的,并且只检索震级至少为 3 的事件,这也是本研究的一个局限性,因为它简化了地震动态。
Kaplan-Meier 估计器是一种在生存分析中使用的非参数方法,用于估计生存函数,进而得出我们的概率。我们将使用包‘lifelines’将 Kaplan-Meier 估计器拟合到每个聚类中的地震数据,以生成概率曲线:
import plotly.graph_objects as go
from lifelines import KaplanMeierFitter
# Initialize Kaplan-Meier Fitter
kmf = KaplanMeierFitter()
# Create a Plotly Figure
fig = go.Figure()
color_map = {0: 'red', 1: 'blue', 2: 'green', -1: 'gray'}
# Fit and plot survival curves for each cluster excluding cluster -1
for label, group in result_df.groupby('cluster_label_hdbscan'):
if label != -1:
durations = group['time_elapsed'].dt.total_seconds() / 3600 # Convert hours to days
event_observed = group['event_happened'].values
kmf.fit(durations=durations, event_observed=a)
# Plot survival function
fig.add_trace(go.Scatter(
x=kmf.survival_function_.index,
y=1-kmf.survival_function_.KM_estimate,
mode='lines',
name=f'Cluster {label}',
line=dict(color=color_map[label])
))
# Customize the plot
fig.update_layout(
xaxis_title='Elapsed Time (hours)',
yaxis_title='Probability of experiencing an earthquake',
legend_title='Cluster Labels',
width=800, # Adjust width
height=500, # Adjust height
margin=dict(l=50, r=20, t=50, b=50), # Adjust margins
legend=dict(x=0.8, y=0.99), # Adjust legend position
)
# Show the plot
fig.show()
在查看结果之前,注意使用我们之前创建的‘event_happened’列的‘event_observed’参数。我们的最终地震是一个右删失数据点的例子,因为没有后续地震发生,因此被视为部分观测。
现在查看这些曲线:
聚类地震区域的生存分析概率曲线。图片由作者提供。
每个地震聚类都有其自己的彩色曲线,其中 X 轴表示地震发生后的时间,Y 轴表示地震发生的概率。一些有趣的观察结果:
-
曲线的形状相似。
-
曲线的初始陡峭程度因聚类位置而异。
让我们聚焦于地震发生后的初始时间,因为我们的大部分数据表明地震通常会在相对较短的时间内接连发生:
聚类地震区域的生存分析概率曲线。图片由作者提供。
这表明地震发生的时间在不同区域是不相同的。通过观察图表,我们可以得出结论:一旦发生地震事件,聚类 0 的区域在十小时内发生另一场地震的概率约为 35%,聚类 1 为 60%,聚类 2 为 48%。这表明聚类 1 区域的地震发生得比其他两个区域更为迅速。
我们可以通过查看每个聚类位置的地震间隔时间的直方图来进行双重检查:
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import numpy as np
# Filter data
filtered_df = result_df[result_df['cluster_label_hdbscan'] != -1]
# Create subplots in a 2 by 2 grid
fig = make_subplots(rows=2, cols=2, subplot_titles=[f'Cluster {label}' for label in filtered_df['cluster_label_hdbscan'].unique()],
shared_xaxes='all', shared_yaxes='all')
# Create histograms for elapsed time for each cluster
for i, (label, group) in enumerate(filtered_df.groupby('cluster_label_hdbscan')):
# Calculate histogram
hist_data, bin_edges = np.histogram(group['time_elapsed'].dt.total_seconds() / 3600, bins=20)
# Add histogram trace to subplot
fig.add_trace(go.Bar(
x=bin_edges,
y=hist_data,
opacity=0.5,
name=f'Cluster {label}',
marker=dict(color=color_map[label])
), row=(i // 2) + 1, col=(i % 2) + 1)
# Customise subplot
fig.update_xaxes(title_text='Elapsed Time (hours)', row=(i // 2) + 1, col=(i % 2) + 1)
fig.update_yaxes(title_text='Frequency', row=(i // 2) + 1, col=(i % 2) + 1)
# Update layout
fig.update_layout(
showlegend=False,
margin=dict(l=50, r=50, t=50, b=50), # Adjust margins
height=600,
width=800,
)
# Show the plot
fig.show()
聚类区域内地震间隔时间的直方图。图片由作者提供。
如概率曲线所示,聚类 1 的地震事件之间的间隔比聚类 2 和聚类 3 更长。
结论
本研究利用了空间聚类和生存分析来揭示地震数据中的时间和地理模式。通过使用如HDBSCAN的空间聚类技术和Kaplan-Meier生存分析估计器,我们获得了关于地震之间时间的区域变化的宝贵见解,这些见解被转化为概率曲线,可用于地震高风险地区的风险评估和应急准备。
感谢阅读!
免责声明:本文仅应作为学习用途。
虚假相关:统计学的喜剧与悲剧
关于统计学的误用
·发表于 Towards Data Science ·阅读时间:17 分钟·2024 年 2 月 23 日
--
作者:Celia Banks 博士和 Paul Boothroyd III
作者
引言
自从Tyler Vigen创造了“虚假相关”这一术语,指的是“从愚蠢数据中挖掘出来的任何随机相关性”(Vigen,2014),参见:Tyler Vigen 的个人网站,之后出现了许多文章,致敬这一将统计学扭曲以使相关性等同于因果关系的危险倾向。参见:HBR(2015)、Medium(2016)、FiveThirtyEight(2016)。作为数据科学家,我们的任务是提供统计分析,接受或拒绝零假设。我们被教导在获取数据、提取数据、预处理数据并做出统计假设时,要保持道德伦理。这不是小事——全球公司依赖于我们分析的有效性和准确性。同样重要的是,我们的工作必须是可复现的。然而,尽管我们被教导实践“正确”的方法,仍然可能会遇到那种时刻(或更多),老板或客户坚持要求你处理数据直到它支持假设,并且最重要的是,展示变量 y 如何在与变量 x 相关时导致变量 x。这就是 p-hacking 的基础,在这种情况下,你进入了一个远离“正确”实践的领域。在本报告中,我们将学习如何利用虚假相关进行错误的研究。我们的目标是深入了解“错误”的方式,从而学习在面对那个不可避免的时刻——需要交付老板或客户耳语的要求时——该做什么或不该做什么。
本项目的目标是教你
关于统计学的误用
我们将展示两个无关变量的虚假相关。来自两个不同来源的数据集经过预处理并合并在一起,以便展示关系的可视化。虚假相关发生在两个变量之间看似相关时,且进一步假设一个变量直接影响另一个变量,从而导致某种结果。我们选择这个项目的原因是我们对如何管理客户对数据分析项目结果期望的方式感兴趣。对于团队成员 Banks 来说,有时她会遇到客户对分析结果表示不满,实际上曾有一次客户要求她回去查看其他数据源和机会,以“帮助”找到他们寻求的答案。是的,这就是 p-hacking——在这种情况下,客户坚持认为因果关系存在,因为他们相信相关性存在并导致了某个结果。
虚假相关的例子
Tyler Vigen 的《虚假相关》摘录。2024 年 2 月 1 日从虚假相关 (tylervigen.com)获取,作者授权转载。
本研究相关的研究问题
研究问题是什么?
我们为什么需要它们?
我们是在做“坏”分析,对吧?
研究问题是研究的基础。它们通过集中研究者将要调查的特定话题来引导研究过程。它们的重要性包括但不限于:聚焦和清晰;作为方法论的指导;确立研究的相关性;帮助构建报告;帮助研究者评估结果并解读发现。在学习如何进行“错误”分析时,我们提出了以下问题:
(1) 数据来源是否有效(不是捏造的)?
(2) 如何处理缺失值?
(3) 你是如何合并不同的数据集的?
(4) 响应变量和预测变量是什么?
(5) 响应变量和预测变量之间的关系是线性的吗?
(6) 响应变量和预测变量之间是否存在相关性?
(7) 我们能否说变量之间存在因果关系?
(8) 如果客户对这两个变量之间的关系感兴趣,你会提供什么解释?
(9) 你在选择的数据集中发现了虚假相关吗?
(10) 在进行这个项目时,你的收获是什么?
方法论
我们是如何进行研究的
虚假相关?
为了研究变量之间是否存在伪相关性,进行了全面的分析。数据集跨越了不同的经济和环境因素领域,这些数据都来自公开来源并得到了确认。这些数据集包含了没有明显因果关系但存在统计相关性的变量。选定的数据集包括 Apple 股票数据(主要数据集)和纽约市每日最高温度数据(次要数据集)。数据集涵盖了 2017 年 1 月至 2022 年 12 月的时间范围。
采用了严格的统计技术来分析数据。计算了皮尔逊相关系数,以量化变量对之间线性关系的强度和方向。为了完成这一分析,使用了纽约市五年期每日最高温度的散点图、Apple 股票五年期趋势的蜡烛图,以及每日最高温度与股票趋势的双轴图表,以可视化变量之间的关系并识别模式或趋势。此方法遵循的领域包括:
数据:来源/提取/处理
主要数据集:Apple 股价历史 | AAPL 公司股票历史价格 | FinancialContent 商业页面
次要数据集:2017 年 1 月到 2022 年 12 月纽约市每日最高温度:www.extremeweatherwatch.com/cities/new-york/year-{year}
数据被确认来自公开来源,并且可用于重复验证。通过五年的时间跨度捕捉数据,提供了对模式、趋势和线性关系的有意义视角。温度读数显示了季节性趋势。对于温度和股票数据,数据点中出现了低谷和高峰。需要注意的是,温度使用华氏度,这是气象设置。我们使用了天文设置来进一步处理数据,以产生更强的伪相关性。虽然数据可以以 csv 或 xls 文件格式下载,但在本次任务中,我们使用了 Python 的 Beautiful Soup 网页抓取 API。
接下来,数据被检查是否有缺失值,以及每个数据集包含了多少条记录。天气数据包含日期、每日最高温度、每日最低温度,而 Apple 股票数据包含日期、开盘价、收盘价、成交量、股价和股票名称。为了合并数据集,日期列需要转化为日期时间格式。使用内连接匹配记录并丢弃不匹配的项。对于 Apple 股票,日期和每日收盘价是关注的列。对于天气数据,日期和每日最高温度是关注的列。
数据:处理
来自 Duarte®幻灯片演示文稿
要正确地做‘不好的事情’,你必须
对数据进行处理,直到你找到
你正在寻找的关系...
我们之前的方法没有得到预期的结果。因此,我们没有使用 2018 年夏季五个美国城市的温度,而是提取了 2017 年 1 月至 2022 年 12 月期间纽约市的五年每日最高气温和苹果股票表现。在进行探索性分析时,我们看到季节和年份之间的相关性较弱。因此,我们的下一步是转换温度数据。我们选择了天文温度而不是气象温度,这使得我们获得了季节间的‘有意义’相关性。
采用新方法后,我们注意到合并数据集时遇到了问题。日期字段不同,天气数据的日期为“月-日”格式,而股票数据的日期为“年-月-日”格式。我们通过将每个数据集的日期列转换为日期时间格式来解决这个问题。此外,每个日期列都按时间顺序或逆时间顺序排序。我们通过将两个日期列按升序排序解决了这一问题。
分析 I:我们是否有虚假相关性?我们能证明吗?
相关性的虚假性质
这里展示的是从
气象季节(春季:3 月-5 月,
夏季:6 月-8 月,秋季:9 月-11 月,冬季:
12 月-2 月)基于气象
北半球的模式,去
天文季节(春季:4 月-6 月,
夏季:7 月-9 月,秋季:10 月-12 月,冬季:
1 月-3 月)基于地球的倾斜角度。
一旦我们完成了探索,分析虚假相关性的一个关键点是确定感兴趣的变量是否相关。我们目测发现 2020 年春季的相关性为 0.81。接着我们确定了是否存在统计显著性——是的,p 值约为 0.000000000000001066818316115281,我认为我们有显著性!
2020 年春季的温度与苹果股票的相关性
分析 II:使用额外的统计数据来检验虚假相关性的性质
如果确实存在虚假相关性,我们可能想要
考虑相关性是否等同于因果关系——即
是否,天文温度的变化是否会引起
苹果股票是否会波动?我们进一步采用了
通过统计检验来证明或否定假设
一个变量是否导致另一个变量。
有许多统计工具用于检验因果关系。工具包括工具变量(IV)分析、面板数据分析、结构方程模型(SEM)、向量自回归模型、协整分析和格兰杰因果关系。IV 分析考虑了回归分析中的遗漏变量;面板数据研究固定效应和随机效应模型;SEM 分析结构关系;向量自回归考虑动态的多变量时间序列互动;协整分析确定变量是否在随机趋势中一起变化。我们需要一个能细致区分真正因果关系和偶然关联的工具。为了实现这一点,我们选择了格兰杰因果关系。
格兰杰因果关系
格兰杰检验检查过去的数值是否能预测未来的数值。在我们的案例中,我们检验了纽约市过去的每日最高气温是否能预测苹果股票价格的未来值。
零假设 Ho:纽约市的每日最高气温不会引起苹果股票价格波动。
为了进行检验,我们运行了 100 个滞后期,看看是否有显著的 p 值。我们遇到接近 1.0 的 p 值,这表明我们不能拒绝零假设,最终得出结论:没有证据表明变量之间存在因果关系。
滞后为 100 时的格兰杰因果关系检验
分析 III:统计学验证未拒绝零假设 Ho
格兰杰因果关系证明了 p 值
在拒绝零假设时不显著
假设。但这足够吗?
让我们验证我们的分析。
为了帮助减少将虚假相关误解为真正因果效应的风险,结合进行交叉相关分析和格兰杰因果关系检验可以确认其结果。使用这种方法,如果存在虚假相关,我们将在某些滞后期看到交叉相关的显著性,但没有一致的因果方向,或者格兰杰因果关系不存在。
交叉相关分析
该方法通过以下步骤完成:
-
检查变量之间相关性的时间模式;
-
•如果变量 A 格兰杰引起变量 B,那么在正滞后期,变量 A 和变量 B 之间会出现显著的交叉相关;
-
在特定滞后期的交叉相关中的显著峰值暗示着因果变量变化之间的时间延迟。
解释:
ccf 和滞后值在某些滞后期显示出正相关的显著性。这确认了虚假相关的存在。然而,像格兰杰因果关系一样,交叉相关分析无法支持因果关系在这两个变量之间的存在。
总结:关键学习
-
虚假相关是一种伪造统计显著性的方法。相关性并不意味着因果关系。
-
即使使用“坏”的数据策略,统计检验也会揭示其缺乏显著性。尽管变量之间存在伪相关性的统计证据,但因果关系检验无法支持因果关系存在的主张。
-
一项研究不能仅仅依赖于变量显示线性关系这一前提来建立因果关系。相反,必须考虑其他影响每个变量的因素。
-
一个非统计性的测试,来判断纽约市的每日高温是否会导致苹果股票波动,可以是考虑:如果你拥有一张苹果股票证书并将其放入冰箱中,证书的价值会受到寒冷的影响吗?类似地,如果你将证书放在一个阳光明媚的炎热日子里,阳光会影响证书的价值吗?
伦理考虑:P-Hacking 不是有效的分析
www.freepik.com/free-vector/business-people-saying-no-concept-illustration_38687005.htm#query=refuse%20work&position=20&from_view=keyword&track=ais&uuid=e5cd742b-f902-40f7-b7c4-812b147fe1df
图片由 storyset 提供,来自 Freepik
伪相关性不是因果关系。
P-hacking 可能会影响你作为一个
数据科学家。做会议中的成年人并
拒绝参与坏统计。
这项研究展示了涉及“坏”统计分析的情况。它演示了数据科学家如何以某种方式获取、提取和操控数据,以统计方式显示相关性。最终,统计检验经受住了挑战,证明了相关性并不等于因果关系。
进行伪相关性分析涉及使用统计数据推导两个无关变量之间的因果关系的伦理问题。这是 p-hacking 的一个例子,它利用统计数据来实现预期的结果。这项研究作为学术研究,旨在展示错误使用统计数据的荒谬性。
另一个伦理考虑领域是网页抓取的实践。许多网站所有者警告不要从他们的网站抓取数据,用于恶意或非他们预期的方式。出于这个原因,像雅虎财经这样的网站使股票数据可下载为 csv 文件。大多数天气网站也是如此,你可以请求温度读数的时间数据集。同样,这项研究是为了学术研究,并展示如何以非传统的方式提取数据。
当面对一个要求你进行 p-hacking 并提供像伪相关性这样的因果关系证明的老板或客户时,要解释他们要求的含义,并且尊重地拒绝这个项目。无论你的决定如何,它都会对你作为数据科学家的信誉产生持久影响。
Banks 博士是I-Meta的首席执行官,该公司开发了获得专利的 Spice Chip 技术,提供针对各行业的大数据分析。Boothroyd 三世先生是退役军事分析员。他们都是曾在美国军队服役并荣誉退役的老兵,且都喜欢讨论虚假的相关性。他们是密歇根大学信息学院 MADS 项目的同学……Go Blue!
参考文献
Aschwanden, Christie. 2016 年 1 月. 你不能相信你阅读的关于营养的内容. FiveThirtyEight. 取自 2024 年 1 月 24 日,fivethirtyeight.com/features/you-cant-trust-what-you-read-about-nutrition/
商业管理:来自杂志的文章。2015 年 6 月. 当心虚假的相关性. 哈佛商业评论. 取自 2024 年 1 月 24 日,hbr.org/2015/06/beware-spurious-correlations
极端天气观察。2017–2023 年. 取自 2024 年 1 月 24 日,www.extremeweatherwatch.com/cities/new-york/year-2017
Financial Content Services, Inc. 苹果股票价格历史 | 历史 AAPL 公司股票价格 | Financial Content 商业页面. 取自 2024 年 1 月 24 日
markets.financialcontent.com/stocks/quote/historical?Symbol=537%3A908440&Year=2019&Month=1&Range=12
Plotlygraphs. 2016 年 7 月. 虚假的相关性. Medium. 取自 2024 年 1 月 24 日,plotlygraphs.medium.com/spurious-correlations-56752fcffb69
Vigen, Tyler. 虚假的相关性. 取自 2024 年 2 月 1 日,www.tylervigen.com/spurious-correlations
Vigen 先生的图表经作者许可转载,许可日期为 2024 年 1 月 31 日。
图片已获得各自所有者的授权。
代码部分
##########################
# IMPORT LIBRARIES SECTION
##########################
# Import web scraping tool
import requests
from bs4 import BeautifulSoup
import pandas as pd
import numpy as np
# Import visualization appropriate libraries
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import seaborn as sns # New York temperature plotting
import plotly.graph_objects as go # Apple stock charting
from pandas.plotting import scatter_matrix # scatterplot matrix
# Import appropriate libraries for New York temperature plotting
import seaborn as sns
import matplotlib.pyplot as plt
from datetime import datetime, timedelta
import re
# Convert day to datetime library
import calendar
# Cross-correlation analysis library
from statsmodels.tsa.stattools import ccf
# Stats library
import scipy.stats as stats
# Granger causality library
from statsmodels.tsa.stattools import grangercausalitytests
##################################################################################
# EXAMINE THE NEW YORK CITY WEATHER AND APPLE STOCK DATA IN READYING FOR MERGE ...
##################################################################################
# Extract New York City weather data for the years 2017 to 2022 for all 12 months
# 5-YEAR NEW YORK CITY TEMPERATURE DATA
# Function to convert 'Day' column to a consistent date format for merging
def convert_nyc_date(day, month_name, year):
month_num = datetime.strptime(month_name, '%B').month
# Extract numeric day using regular expression
day_match = re.search(r'\d+', day)
day_value = int(day_match.group()) if day_match else 1
date_str = f"{month_num:02d}-{day_value:02d}-{year}"
try:
return pd.to_datetime(date_str, format='%m-%d-%Y')
except ValueError:
return pd.to_datetime(date_str, errors='coerce')
# Set variables
years = range(2017, 2023)
all_data = [] # Initialize an empty list to store data for all years
# Enter for loop
for year in years:
url = f'https://www.extremeweatherwatch.com/cities/new-york/year-{year}'
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
div_container = soup.find('div', {'class': 'page city-year-page'})
if div_container:
select_month = div_container.find('select', {'class': 'form-control url-selector'})
if select_month:
monthly_data = []
for option in select_month.find_all('option'):
month_name = option.text.strip().lower()
h5_tag = soup.find('a', {'name': option['value'][1:]}).find_next('h5', {'class': 'mt-4'})
if h5_tag:
responsive_div = h5_tag.find_next('div', {'class': 'responsive'})
table = responsive_div.find('table', {'class': 'bordered-table daily-table'})
if table:
data = []
for row in table.find_all('tr')[1:]:
cols = row.find_all('td')
day = cols[0].text.strip()
high_temp = float(cols[1].text.strip())
data.append([convert_nyc_date(day, month_name, year), high_temp])
monthly_df = pd.DataFrame(data, columns=['Date', 'High (°F)'])
monthly_data.append(monthly_df)
else:
print(f"Table not found for {month_name.capitalize()} {year}")
else:
print(f"h5 tag not found for {month_name.capitalize()} {year}")
# Concatenate monthly data to form the complete dataframe for the year
yearly_nyc_df = pd.concat(monthly_data, ignore_index=True)
# Extract month name from the 'Date' column
yearly_nyc_df['Month'] = yearly_nyc_df['Date'].dt.strftime('%B')
# Capitalize the month names
yearly_nyc_df['Month'] = yearly_nyc_df['Month'].str.capitalize()
all_data.append(yearly_nyc_df)
######################################################################################################
# Generate a time series plot of the 5-year New York City daily high temperatures
######################################################################################################
# Concatenate the data for all years
if all_data:
combined_df = pd.concat(all_data, ignore_index=True)
# Create a line plot for each year
plt.figure(figsize=(12, 6))
sns.lineplot(data=combined_df, x='Date', y='High (°F)', hue=combined_df['Date'].dt.year)
plt.title('New York City Daily High Temperature Time Series (2017-2022) - 5-Year Trend', fontsize=18)
plt.xlabel('Date', fontsize=16) # Set x-axis label
plt.ylabel('High Temperature (°F)', fontsize=16) # Set y-axis label
plt.legend(title='Year', bbox_to_anchor=(1.05, 1), loc='upper left', fontsize=14) # Display legend outside the plot
plt.tick_params(axis='both', which='major', labelsize=14) # Set font size for both axes' ticks
plt.show()
# APPLE STOCK CODE
# Set variables
years = range(2017, 2023)
data = [] # Initialize an empty list to store data for all years
# Extract Apple's historical data for the years 2017 to 2022
for year in years:
url = f'https://markets.financialcontent.com/stocks/quote/historical?Symbol=537%3A908440&Year={year}&Month=12&Range=12'
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
table = soup.find('table', {'class': 'quote_detailed_price_table'})
if table:
for row in table.find_all('tr')[1:]:
cols = row.find_all('td')
date = cols[0].text
# Check if the year is within the desired range
if str(year) in date:
open_price = cols[1].text
high = cols[2].text
low = cols[3].text
close = cols[4].text
volume = cols[5].text
change_percent = cols[6].text
data.append([date, open_price, high, low, close, volume, change_percent])
# Create a DataFrame from the extracted data
apple_df = pd.DataFrame(data, columns=['Date', 'Open', 'High', 'Low', 'Close', 'Volume', 'Change(%)'])
# Verify that DataFrame contains 5-years
# apple_df.head(50)
#################################################################
# Generate a Candlestick charting of the 5-year stock performance
#################################################################
new_apple_df = apple_df.copy()
# Convert Apple 'Date' column to a consistent date format
new_apple_df['Date'] = pd.to_datetime(new_apple_df['Date'], format='%b %d, %Y')
# Sort the datasets by 'Date' in ascending order
new_apple_df = new_apple_df.sort_values('Date')
# Convert numerical columns to float, handling empty strings
numeric_cols = ['Open', 'High', 'Low', 'Close', 'Volume', 'Change(%)']
for col in numeric_cols:
new_apple_df[col] = pd.to_numeric(new_apple_df[col], errors='coerce')
# Create a candlestick chart
fig = go.Figure(data=[go.Candlestick(x=new_apple_df['Date'],
open=new_apple_df['Open'],
high=new_apple_df['High'],
low=new_apple_df['Low'],
close=new_apple_df['Close'])])
# Set the layout
fig.update_layout(title='Apple Stock Candlestick Chart',
xaxis_title='Date',
yaxis_title='Stock Price',
xaxis_rangeslider_visible=False,
font=dict(
family="Arial",
size=16,
color="Black"
),
title_font=dict(
family="Arial",
size=20,
color="Black"
),
xaxis=dict(
title=dict(
text="Date",
font=dict(
family="Arial",
size=18,
color="Black"
)
),
tickfont=dict(
family="Arial",
size=16,
color="Black"
)
),
yaxis=dict(
title=dict(
text="Stock Price",
font=dict(
family="Arial",
size=18,
color="Black"
)
),
tickfont=dict(
family="Arial",
size=16,
color="Black"
)
)
)
# Show the chart
fig.show()
##########################################
# MERGE THE NEW_NYC_DF WITH NEW_APPLE_DF
##########################################
# Convert the 'Day' column in New York City combined_df to a consistent date format ...
new_nyc_df = combined_df.copy()
# Add missing weekends to NYC temperature data
start_date = new_nyc_df['Date'].min()
end_date = new_nyc_df['Date'].max()
weekend_dates = pd.date_range(start_date, end_date, freq='B') # B: business day frequency (excludes weekends)
missing_weekends = weekend_dates[~weekend_dates.isin(new_nyc_df['Date'])]
missing_data = pd.DataFrame({'Date': missing_weekends, 'High (°F)': None})
new_nyc_df = pd.concat([new_nyc_df, missing_data]).sort_values('Date').reset_index(drop=True) # Resetting index
new_apple_df = apple_df.copy()
# Convert Apple 'Date' column to a consistent date format
new_apple_df['Date'] = pd.to_datetime(new_apple_df['Date'], format='%b %d, %Y')
# Sort the datasets by 'Date' in ascending order
new_nyc_df = combined_df.sort_values('Date')
new_apple_df = new_apple_df.sort_values('Date')
# Merge the datasets on the 'Date' column
merged_df = pd.merge(new_apple_df, new_nyc_df, on='Date', how='inner')
# Verify the correct merge -- should merge only NYC temp records that match with Apple stock records by Date
merged_df
# Ensure the columns of interest are numeric
merged_df['High (°F)'] = pd.to_numeric(merged_df['High (°F)'], errors='coerce')
merged_df['Close'] = pd.to_numeric(merged_df['Close'], errors='coerce')
# UPDATED CODE BY PAUL USES ASTRONOMICAL TEMPERATURES
# CORRELATION HEATMAP OF YEAR-OVER-YEAR
# DAILY HIGH NYC TEMPERATURES VS.
# APPLE STOCK 2017-2023
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
# Convert 'Date' to datetime
merged_df['Date'] = pd.to_datetime(merged_df['Date'])
# Define a function to map months to seasons
def map_season(month):
if month in [4, 5, 6]:
return 'Spring'
elif month in [7, 8, 9]:
return 'Summer'
elif month in [10, 11, 12]:
return 'Fall'
else:
return 'Winter'
# Extract month from the Date column and map it to seasons
merged_df['Season'] = merged_df['Date'].dt.month.map(map_season)
# Extract the years present in the data
years = merged_df['Date'].dt.year.unique()
# Create subplots for each combination of year and season
seasons = ['Spring', 'Summer', 'Fall', 'Winter']
# Convert 'Close' column to numeric
merged_df['Close'] = pd.to_numeric(merged_df['Close'], errors='coerce')
# Create an empty DataFrame to store correlation matrix
corr_matrix = pd.DataFrame(index=years, columns=seasons)
# Calculate correlation matrix for each combination of year and season
for year in years:
year_data = merged_df[merged_df['Date'].dt.year == year]
for season in seasons:
data = year_data[year_data['Season'] == season]
corr = data['High (°F)'].corr(data['Close'])
corr_matrix.loc[year, season] = corr
# Plot correlation matrix
plt.figure(figsize=(10, 6))
sns.heatmap(corr_matrix.astype(float), annot=True, cmap='coolwarm', fmt=".2f")
plt.title('Temperature-Stock Correlation', fontsize=18) # Set main title font size
plt.xlabel('Season', fontsize=16) # Set x-axis label font size
plt.ylabel('Year', fontsize=16) # Set y-axis label font size
plt.tick_params(axis='both', which='major', labelsize=14) # Set annotation font size
plt.tight_layout()
plt.show()
#######################
# STAT ANALYSIS SECTION
#######################
#############################################################
# GRANGER CAUSALITY TEST
# test whether past values of temperature (or stock prices)
# can predict future values of stock prices (or temperature).
# perform the Granger causality test between 'High (°F)' and
# 'Close' columns in merged_df up to a maximum lag of 255
#############################################################
# Perform Granger causality test
max_lag = 1 # Choose the maximum lag of 100 - Jupyter times out at higher lags
test_results = grangercausalitytests(merged_df[['High (°F)', 'Close']], max_lag)
# Interpretation:
# looks like none of the lag give a significant p-value
# at alpha .05, we cannot reject the null hypothesis, that is,
# we cannot conclude that Granger causality exists between daily high
# temperatures in NYC and Apple stock
#################################################################
# CROSS-CORRELATION ANALYSIS
# calculate the cross-correlation between 'High (°F)' and 'Close'
# columns in merged_df, and ccf_values will contain the
# cross-correlation coefficients, while lag_values will
# contain the corresponding lag values
#################################################################
# Calculate cross-correlation
ccf_values = ccf(merged_df['High (°F)'], merged_df['Close'])
lag_values = np.arange(-len(merged_df)+1, len(merged_df))
ccf_values, lag_values
# Interpretation:
# Looks like there is strong positive correlation in the variables
# in latter years and positive correlation in their respective
# lags. This confirms what our plotting shows us
########################################################
# LOOK AT THE BEST CORRELATION COEFFICIENT - 2020? LET'S
# EXPLORE FURTHER AND CALCULATE THE p-VALUE AND
# CONFIDENCE INTERVAL
########################################################
# Get dataframes for specific periods of spurious correlation
merged_df['year'] = merged_df['Date'].dt.year
best_season_data = merged_df.loc[(merged_df['year'] == 2020) & (merged_df['Season'] == 'Spring')]
# Calculate correlation coefficient and p-value
corr_coeff, p_value = stats.pearsonr(best_season_data['High (°F)'], best_season_data['Close'])
corr_coeff, p_value
# Perform bootstrapping to obtain confidence interval
def bootstrap_corr(data, n_bootstrap=1000):
corr_values = []
for _ in range(n_bootstrap):
sample = data.sample(n=len(data), replace=True)
corr_coeff, _ = stats.pearsonr(sample['High (°F)'], sample['Close'])
corr_values.append(corr_coeff)
return np.percentile(corr_values, [2.5, 97.5]) # 95% confidence interval
confidence_interval = bootstrap_corr(best_season_data)
confidence_interval
#####################################################################
# VISUALIZE RELATIONSHIP BETWEEN APPLE STOCK AND NYC DAILY HIGH TEMPS
#####################################################################
# Dual y-axis plotting using twinx() function from matplotlib
date = merged_df['Date']
temperature = merged_df['High (°F)']
stock_close = merged_df['Close']
# Create a figure and axis
fig, ax1 = plt.subplots(figsize=(10, 6))
# Plotting temperature on the left y-axis (ax1)
color = 'tab:red'
ax1.set_xlabel('Date', fontsize=16)
ax1.set_ylabel('Temperature (°F)', color=color, fontsize=16)
ax1.plot(date, temperature, color=color)
ax1.tick_params(axis='y', labelcolor=color)
# Create a secondary y-axis for the stock close prices
ax2 = ax1.twinx()
color = 'tab:blue'
ax2.set_ylabel('Stock Close Price', color=color, fontsize=16)
ax2.plot(date, stock_close, color=color)
ax2.tick_params(axis='y', labelcolor=color)
# Title and show the plot
plt.title('Apple Stock correlates with New York City Temperature', fontsize=18)
plt.show()
SQL 和数据建模实战:深入探讨数据湖仓
·发布于数据科学之路 ·阅读时间 10 分钟·2024 年 10 月 21 日
--
任何从事商业智能、数据科学、数据分析或云计算工作的人,都会在某个时候接触到 SQL。我们可以用它来提取、操作和分析数据——无论是在关系型数据库中,还是在现代云环境中。为了实施 Salesforce 数据云,我不得不复习一下我的 SQL 和数据建模知识。
有趣的事实:你知道最常用的 SQL 命令是什么吗?
阅读完整篇文章,你将会找到答案 😉
内容1 — 什么是数据湖、数据仓库和数据湖仓?
2 — 它与商业智能工具和云存储有什么区别?
3 — 为什么 SQL 和数据建模对于数据湖仓或特定工具(如 Salesforce 数据云)很重要
4 — 云应用程序中的 SQL 和数据建模基础
5 — Salesforce 数据云与其他云工具的区别
6 — 数据湖仓对数据科学家的关键使用案例
7. 最后的思考
1 — 什么是数据湖、数据仓库和数据湖仓?
当我们谈论数据平台或数据架构时,需要理解数据湖、数据仓库和数据湖仓的概念。由于我们生活在一个数据日益增长的世界中——几年前总是有人说“数据是新的黄金”——因此,我们也需要能够存储、处理和利用大量数据的系统……
SQL 解释:公共表表达式
图片来自 AI(Dalle-3)
什么是 CTE,它们如何使用
·发表于 Towards Data Science ·阅读时间:8 分钟·2024 年 5 月 22 日
--
SQL 中的公共表表达式(或简称 CTE)是临时的、命名的结果集,包含从另一个 SQL 查询派生的中间数据。一旦你在 CTE 中有了数据,你可以在同一查询中多次引用这些数据。
根据上述描述,你可能会认为 CTE 听起来像 SQL 中的常规临时表,从某些方面来看,它们确实相似。那么,为什么要使用 CTE 而不是临时表呢?为了回答这个问题,我们需要了解临时表的两个主要缺点。
其中一个缺点是临时表可能导致代码更加复杂,尤其是在大型 SQL 脚本的不同部分之间使用时。它们需要明确的创建、删除,可能还需要为其创建索引,这增加了 SQL 和会话管理的开销。
其次,临时表会消耗物理存储空间,如果空间紧张且有大量临时表时,这可能是一个需要考虑的因素。此外,当查看使用临时表的查询时,可能不清楚临时表中包含了哪些数据以及这些数据来自何处。
公共表表达式(CTE)没有上述问题。首先,它们是短暂的,一旦 SQL 会话结束,CTE 就会超出作用域,任何...
SQL 解释:分组集、汇总和立方体
图片来自 AI(Dalle-3)
它们是什么,如何使用它们?
·发布于 Towards Data Science ·7 分钟阅读·2024 年 5 月 9 日
--
我是 SQL 的长期用户,但有一件事我一直没能完全掌握,那就是现代 SQL 数据库系统在多年前引入的分组集(grouping sets)、立方体(cube)和汇总(rollup)功能。
其中一个原因可能是我所使用和查询的数据库类型。它们大多是 OLTP 数据库系统,而在我的日常工作中,我似乎从未需要使用这些运算符。
我的另一部分其实并不完全理解它们是如何工作的。我发现相关文档令人困惑,因此我最终将它们搁置在了比喻意义上的“知道就好,但不需要它们”堆里。
那么,发生了什么变化呢?实际上没有变化,但就个人而言,我就是讨厌当我所使用的系统引入我完全不理解或不用的功能时。
我常常回想起窗口函数首次成为 SQL 的一部分时的类似情况。我立即接受了它们,大量使用它们,变得相当熟练,并能够用它们做一些在常规 SQL 中非常困难甚至不可能做到的事情。
所以,可能是因为我工作中使用的数据库类型,我花了十多年才重新审视这些分组功能……
SQL 解释:规范化范式
图像来源:AI(Dalle-3)
将第一、第二和第三范式应用于数据库
·发表于Towards Data Science ·阅读时间 10 分钟·2024 年 5 月 29 日
--
数据库系统中的规范化指的是将数据结构化,以减少数据冗余并提高数据完整性的过程。
它涉及将数据库分解为多个表,并根据一组称为范式的规则定义表之间的关系。
目标是消除冗余数据,并确保数据之间的相互依赖关系是合理的。
尽管有许多不同类型的规范化范式,但对于大多数数据库系统来说,应用前三种范式(1NF、2NF 和 3NF)就足以实现一个结构良好且优化的数据库设计。
在本文中,我们将从一个非常简单的单表开始,该表未经过规范化,然后对其应用三种范式,并对从该过程中得到的任何后续表格进行处理,看看最终的设计结果如何。
在此之前,简要解释一下 1NF、2NF 和 3NF。
第一范式(1NF)
为了使表格符合 1NF,它必须满足以下标准。
- 每一行必须是唯一可识别的。这可以通过在表格的一个或多个列上定义主键来实现。
SQL 解析:排名分析
图片来源:AI(Dalle-3)
它们是什么,以及如何使用它们
·发布于 Towards Data Science ·13 分钟阅读·2024 年 6 月 11 日
--
如果你有使用过像 Oracle、SQL Server、Postgres 等流行的关系型数据库管理系统(RDBMS)的经验,你可能在某个阶段遇到过分析函数,有时也叫窗口函数。
分析功能允许你计算数据集中每组行的聚合和排名序列。如果你曾经怀疑是否值得投入一些时间来了解和使用分析函数,我可以告诉你,答案是肯定的。它们非常有用,能够让 SQL 执行一些没有它们就难以甚至不可能完成的任务。
在本文中,我们将专注于一种特定类别的分析——排名函数,并通过分析 SQL 中四种最常用的排名技术来进行讲解。我们将解释它们是什么,并提供使用示例。
排名分析语法
大多数现代 SQL 方言中,排名分析函数的一般格式是:
rank | dense_rank | row_number() | ntile
over(partition_by_clause order_by_clause windowing_clause)
让我们简要地依次查看上面语句的每个部分。
数据科学所需的 SQL 知识
成为 SQL 熟练者的主题、资源和建议。
·发表于 Towards Data Science ·阅读时间:9 分钟·2024 年 6 月 8 日
--
根据 365DataScience 的一篇文章,这篇文章调查了 1000 个 LinkedIn 上的数据科学职位招聘信息,其中 60% 要求 SQL。
这告诉了我们什么?
好吧,SQL 是数据科学家必须掌握的一项基本技能,如果他们想提高找到工作的机会的话。
在这篇文章中,我将讨论你应该掌握的 SQL 知识,以便获得入门级数据科学岗位,并提供一些在我学习 SQL 时帮助过我的资源和建议。
知识
根据我的经验,对于许多入门级的数据科学职位,你不需要成为 SQL 专家;你只需要知道如何查询并获取你需要分析的数据,并构建机器学习模型。
例如,创建 ETL(提取、转换、加载)管道和管理数据库通常不属于数据科学家的职责,而是数据工程师的工作。然而,这不应该成为你的障碍……
SQL:数据工程 — 第一部分
SQL 在数据驱动决策中的应用
·发表于Towards Data Science ·48 分钟阅读·2024 年 9 月 20 日
--
这里有一个有趣的事实:你知道 SQL 语言是什么时候创建的吗?它首次出现是什么时候?我知道!那是在1974 年。那时我还没出生,人们已经在使用 SQL 了。今天,快 50 年过去了,它依然年轻如初。
SQL 是数据科学领域中使用的主要工具之一。在某个时刻,你可能会用到 SQL:无论是访问数据库、准备数据管道,还是加载数据。
无论你是在处理数据科学任务、数据准备还是数据工程,都不重要。SQL 很可能会在某个时刻成为你工作的组成部分。任何从事数据工作的人,都会在某个项目中使用 SQL。
我的目标是为你提供一个关于 SQL 的完整介绍,重点关注数据工程。我将介绍主要的命令,并逐步深入更复杂的主题,演示各种动手操作。
当然,在你日常的工作中,总会有接触到 SQL 的时候。没有办法避免,好吗?你必须将它加入到你的工具箱中。你知道的工具越多,你能解决的问题就越多。
另外,请尽量拍手👏👏,你能拍多少就拍多少,我为这篇文章付出了很多心血。走吧!🐼
SQL:数据工程——第二部分。
用 SQL 进行数据驱动决策
·发表于Towards Data Science ·42 分钟阅读·2024 年 10 月 1 日
--
图片由SOON SANTOS提供,来源于Unsplash
非常感谢你至今与我同行! 本系列的第一部分为我们打下了基础,现在是时候更深入地探索了。
在下一个阶段,我们将超越之前的sc01
模式,开始攻克更高级的数据工程概念。
我真心感谢你在这段旅程中给予的所有支持和鼓励。
你的反馈和热情让我保持灵感,并激励我继续与大家一起探索这些话题。让我们保持这股动力继续前进!👏👏
解锁 SQL 在数据驱动决策中的强大力量
towardsdatascience.com
现在,使用新的schema
,让我们准备数据库,创建表格,插入数据,并继续前进。
右键点击“Schemas”,打开查询工具,我们将看到一个空白区域,如右侧图片所示,从而重新开始我们的旅程。
SQL 精通:数据专业人士的高级技术
提升你的数据技能:窗口函数、正则表达式和 CTE
·发布于 Towards Data Science ·阅读时间:6 分钟·2024 年 4 月 1 日
--
在我担任 Chime 的首席数据分析师期间,三项关键的 SQL 技术——窗口函数、正则表达式和 CTE——极大地提升了我的能力,使我从中级水平提升到适合担任首席分析师角色的专业水平。本文详细介绍了这些技术,帮助你提升技能,开启数据探索的新维度。
由我创建的图片,使用 DALL-E 生成
窗口函数
窗口函数(或分析函数)在多个与当前行相关的行之间进行计算,并允许你计算如下内容:
-
排名
-
累计总和
-
7 天移动平均(即当前行之前 7 行的平均值)
使用窗口函数创建排名是一种在分析和数据科学中非常强大的技术。考虑这个transactions
数据集,我们有客户的交易记录。
一个样本交易表截图,由我使用 ChatGPT 创建的虚拟数据。
排名窗口函数:
SQL 优化、数据科学投资组合及其他七月必读文章
·发布于 Towards Data Science ·作为 Newsletter 发送 ·4 分钟阅读 ·2024 年 8 月 1 日
--
想写第一篇 TDS 文章吗?我们始终欢迎新作者的投稿。
我们过去一个月最受阅读和讨论的文章表明,无论是极端的夏季天气还是全球体育赛事,都无法阻止我们的读者提升技能,拓展新兴话题的知识。
我们的月度亮点涵盖了数据科学职业路径、前沿的 LLM 工作流,以及始终相关的 SQL 和 Python 话题。它们由我们的作者用通俗易懂和专业知识相结合的方式呈现,因此,如果你错过了其中任何一篇,希望你能喜欢我们七月的必读文章。(如果炎热的气温影响了你的注意力——我们知道那种感觉!——你会高兴地知道,下面除了两篇文章外,其他文章的阅读时间都在 10 分钟以内。)
月度亮点
-
精通 SQL 优化:从功能性查询到高效查询 谁能拒绝在运行查询时节省大量时间呢?Yu Dong的 SQL 优化实用指南通过提供六个高级技巧引起了广泛关注,这些技巧帮助她在最近的工作中每天减少了 50 小时的查询运行时间;这些技巧尤其适用于在 Snowflake SQL 中工作的数据专业人士。
-
使用 Python、Markdown、Git 和 GitHub Pages 构建专业作品集的完整指南 清晰、快速,并充满有用的代码片段,Pierre-Etienne Toulemonde的首篇 TDS 文章通过引导读者完成构建一个符合两个关键标准的顶级专业作品集的过程而大获成功:免费解决方案和最小配置。
-
运行本地 LLM 比你想象的更有用且更容易 随着 LLM 的使用在我们的日常工作流程中越来越广泛和深入,运行这些强大模型的需求也在增加。Guillaume Weingertner 分享了一个简明的逐步指南,展示了如何让这一过程不再是复杂且资源密集的。
-
数据科学的演变:现代全栈数据科学家所需的新技能 “然而,剧变的是商业期望、技术环境以及数据科学家需要掌握的技能范围。”Col Jung 带我们走过了数据科学的历史,并概述了从业者今天需要掌握的技能类型,以保持竞争力。
-
通过实践领导:作为数据科学经理的经验教训,以及为何我选择回归个人贡献者角色 成功的数据科学职业并不依赖于特定的职称或组织架构位置;正如 Dasha Herrmannova 博士 在对自己近期职业变动的深思熟虑反思中所论述的那样,成功的最重要因素是了解自己的优先事项,并找到一个适合它们的角色,而不是反过来。
-
使用大型语言模型进行文档解析——附代码 本月我们很高兴迎来了Zoumana Keita的作品,尤其是这篇文章,它是一篇循序渐进、易于跟随的教程,讲解了一个对 LLM 应用前景非常有前途的领域:文档解析(本案例中是科学研究论文的 PDF 文件)。
-
在 TensorFlow(以及 PyTorch)中实现神经网络 本月的亮点之一是Shreya Rao在其《深度学习图解》系列中的最新作品:一本为任何想要通过动手实践加深对 Shreya 在之前文章中介绍的理论概念理解的读者提供的实用实现指南。跟随文章学习如何在 TensorFlow 中构建神经网络(此外还有 PyTorch 部分!)。
我们最新的一批新作者
每个月,我们都很高兴看到一批新作者加入 TDS,他们各自分享自己独特的声音、知识和经验。如果你在寻找新的作者来探索和关注,只需浏览我们最新加入的作者们的作品,包括 Jason Zhong、Don Robert Stimpson、Nicholas DiSalvo、Rudra Sinha、Harys Dalvi、Blake Norrish、Nathan Bos, Ph.D.、Ashish Abraham、Jignesh Patel、Shreya Shukla、Vinícius Hector、Fima Furman、Kaizad Wadia、Tomas Jancovic (It's AI Thomas)、Laurin Heilmeyer、Li Yin、Kunal Kambo Puri、Mourjo Sen、Rahul Vir、Meghan Heintz、Dron Mongia、Mahsa Ebrahimian、Pierre-Etienne Toulemonde、Shashank Sharma、Anders Ohrn、Alex Davis、Badr Alabsi, PhD、Jubayer Hossain Ahad、Adesh Nalpet Adimurthy、Mariusz Kujawski、Arieda Muço、Sachin Khandewal、Cai Parry-Jones、Martin Jurran、Alicja Dobrzeniecka、Anna Gordun Peiro、Robert Etter、Christabelle Santos、Sachin Hosmani、和 Jiayan Yin。
感谢您支持我们作者的工作!我们很喜欢发布新作者的文章,因此如果您最近写了一篇有趣的项目演示、教程或关于我们核心主题的理论反思,请不要犹豫,与我们分享。
直到下一个变量,
TDS 团队
SQL Server 的秘密功能 — 在 SQL Server 中本地运行 Python 和附加组件
导入 Python 库,操作和输出 SQL 表等,一切都可以在 SQL Server 中完成,无需离开 SQL Server。
·发表于 Towards Data Science ·阅读时长 8 分钟·2024 年 5 月 15 日
--
问题
在这个项目中,我们面临着管理 37,000 个公司名称的挑战,这些公司名称来源于两个不同的来源。复杂性在于这些来源中相同公司可能被列出为不同的名称。
目标
本文的目标是教你如何在 Microsoft SQL Server 中本地运行 Python。使用附加组件和外部库,以及对生成的表进行进一步的 SQL 处理。
图片来源:Christin Hume 在 Unsplash 上
初步算法构建
这是我在构建算法时遵循的策略:
-
阻塞 — 根据共同属性将数据集划分为更小的块或组,以减少比较记录时的计算复杂度。它缩小了搜索空间,提高了相似性搜索任务的效率。
-
预处理 — 清洗和标准化原始数据,为分析做好准备,包括小写转换、标点符号移除和停止词去除等任务。此步骤提高了数据质量,减少了噪音。
-
相似性搜索模型应用 — 应用模型计算基于标记化表示的记录对之间的相似性或距离。这有助于识别相似的记录对,使用诸如余弦相似度或编辑距离等度量,进行记录连接或去重等任务。
阻塞
我的数据集非常不平衡——我在一个表中有 1,361,373 个实体,在第二个表中只有 37,171 个公司名称。如果我试图在未经处理的表上进行匹配,算法将需要非常长的时间。
为了封锁表,我们需要查看 2 个数据集之间的共同特征。在我的案例中,公司都与内部项目相关。因此,我将执行以下操作:
-
从较小的表中提取唯一的公司名称和项目代码。
-
遍历项目代码,并尝试在较大的表中找到它们。
-
映射该项目的所有基金并将其从大表中提取。
-
为下一个项目重复!
通过这种方式,我将在每次迭代中减少大型数据集,同时确保由于项目级别的小型过滤数据集,映射过程迅速。
一个简单的脚本,用于提取唯一的项目代码和基金名称。
现在,我将按项目代码过滤两个表,像这样:
基于项目代码过滤表的代码示例。
通过这种方法,我们的小表格仅包含 406 行项目“ABC”的数据来进行映射,而大表格则有 15,973 行数据需要匹配。这是从原始表格中大幅度减少的数据量。
程序结构
该项目将包含 Python 和 SQL 函数,运行在 SQL 服务器上;以下是一个程序如何工作的简要概述,旨在帮助更清晰地理解每个步骤:
程序结构。图片由作者创建。
程序执行:
- 在循环中打印项目代码是该函数的最简单版本:
递归打印公司名称的代码。
很快就可以看出 SQL 游标消耗了过多的资源。简而言之,这是因为游标是在行级别操作的,并且会遍历每一行以执行操作。
关于为什么 SQL 中的游标效率低,最好避免使用它们的更多信息可以在这里找到:
stackoverflow.com/questions/4568464/sql-server-temporary-tables-vs-cursors
(答案 2)
为了提高性能,我将使用临时表并移除游标。以下是得到的函数:
一个函数,用于根据项目代码从大型映射表中选择所有值。
现在,每个项目需要大约 3 秒钟的时间来选择项目代码及其相关数据,并从大型映射表中过滤出该项目的数据。
为了演示,我将只关注 2 个项目,然而,在生产环境中,我会在所有项目上运行该函数。
我们将使用的最终函数如下所示:
我已注释掉函数定义,以便让代码更容易调试,并且限制为前 2 个项目
映射表准备
下一步是为 Python 预处理和映射函数准备数据,为此我们将需要 2 个数据集:
-
来自大映射表按项目代码过滤的数据
-
来自小型公司表按项目代码过滤的数据
这是使用来自 2 个表的数据选择后更新的函数样子:
从数据库中选择小型公司表和大映射表。
重要提示:SQL 中的 Python 函数只接受1 个表格输入。确保在将数据输入 SQL 中的 Python 函数之前,将数据放入单一宽表中。
带有来源的表格
作为此功能的结果,我们获得了每个项目的项目、公司名称和来源。
现在我们准备好使用 Python 了!
SQL 中的 Python 执行
通过 sp_execute_external_script
,SQL Server 允许你直接在 SQL Server 中运行 Python 代码。
它使得将 Python 的功能集成到 SQL 工作流中成为可能,并实现 SQL 与 Python 之间的数据交换。在提供的示例中,执行了一个 Python 脚本,从输入数据中创建了一个 pandas DataFrame。
结果作为单个输出返回。
多酷啊!
在 SQL 中运行 Python 时有几个重要事项需要注意:
-
字符串应由双引号(“)定义,而不是单引号(‘)。尤其在使用正则表达式时,请确保检查这一点,以避免在错误跟踪上浪费时间。
-
只允许 1 个输出——因此你的 Python 代码将产生 1 个输出表。
-
你可以使用
print
语句进行调试,并查看结果在 SQL 服务器的“消息”选项卡中打印出来。如下所示:
图像由作者制作。
SQL 中的 Python 库
在 SQL Server 中,几个库是预安装的并且可以直接访问。要查看这些库的完整列表,可以执行以下命令:
用于检索 SQL 中所有可用 Python 库的代码
下面是输出的样子:
你可以像在普通 Python 脚本中一样导入这些包(import ...)。图像由作者制作。
使用 Python 匹配文本
回到我们生成的表格,现在我们可以使用 Python 匹配来自不同来源的公司名称。我们的 Python 程序将接受长表并输出一个包含映射实体的表。它应该显示它认为最有可能匹配的小公司表中的每个记录旁边的大映射表中的匹配项。
假设公司 1.1 是公司 1 的最接近匹配项,输出应该看起来像上面的输出。图像由作者制作。
为了实现这个目标,首先让我们向 SQL 过程添加一个 Python 函数。第一步是将数据集简单地输入到 Python 中,我将使用一个示例数据集,然后再使用我们的数据,以下是代码:
将数据输入到数据库中的代码——这两个表都存在于 Python 函数中。
该系统允许我们将两个表都作为输入传递给 Python 函数,然后它会将两个表作为输出打印出来。
Python 中的预处理
为了有效地匹配字符串,我们必须在 Python 中进行一些预处理,这包括:
-
去除重音符号和其他语言特有的特殊字符
-
去除空格
-
去除标点符号
第一步将通过 SQL 中的排序完成,而其他两步将在 Python 函数的预处理步骤中完成。
这是带有预处理的函数样子:
结果是三个列,一个是小写且无空格的公司名称,第二列是项目列,第三列是来源列。
在 Python 中匹配字符串
在这里我们需要发挥创意,因为我们可以使用的库数量非常有限。因此,让我们首先确定输出应该是什么样子的。
我们希望将来自来源 2 的数据与来源 1 中的数据进行匹配。因此,对于来源 2 中的每个值,我们应该有一堆来自来源 1 的匹配值,并附带得分表示匹配的紧密度。
输出表结构。图像由作者创建。
我们将首先使用Python 内置库,以避免需要导入外部库,从而简化工作。
逻辑:
-
遍历每个项目
-
根据资金来源制作一个表格,其中来源 1 是带有映射数据的大表,来源 2 是最初的公司数据集
-
从小型数据集中选择数据并将其放入数组
-
将结果数组中的每个元素与大映射数据框中的每个元素进行比较
-
返回每个实体的得分
代码:
用于将大数据集的数据映射到小数据集子集的代码。请记住使用你自己的连接和数据结构。
这是最终的输出:
这是为了演示结果而创建的虚拟数据,然而结构应该与你的数据集相同。图像由作者生成。
在这个表格中,我们有每个公司名称、它所属的项目以及来源——无论是来自大的映射表还是小的公司表。右侧的得分表示来源 2 中的公司名称与来源 1 之间的相似度度量。需要注意的是,来自来源 2 的 company4 将始终有一个 1 的得分——100%的匹配度,因为它与自身进行匹配。
通过机器学习服务在 SQL Server 中执行 Python 脚本是一个强大的功能,允许在数据库内进行分析和机器学习任务。此集成使得数据可以直接访问,无需数据迁移,从而显著优化性能和数据密集型操作的安全性。
然而,需要注意的是存在一些限制。该环境支持单一输入,这可能限制了可以在 SQL 上下文中直接执行的任务的复杂性。此外,只有有限的 Python 库可用,这可能需要为某些不被默认库支持的数据分析或机器学习任务寻找替代方案。此外,用户还需要应对 SQL Server 环境中的一些复杂性,比如包含 Python 代码的 T-SQL 查询中的复杂空格,这可能会导致错误和困惑。
尽管存在这些挑战,但在许多场景中,在 SQL Server 中执行 Python 是非常有优势的:
1. 数据清理和转换 — Python 可以直接在 SQL Server 中使用,执行复杂的数据预处理任务,如处理缺失数据或规范化数值,之后再进行进一步的分析或报告。
2. 预测分析 — 在 SQL Server 中直接部署 Python 机器学习模型可以实现实时预测,如客户流失或销售预测,使用的是实时数据库数据。
3. 高级分析 — Python 的功能可以用来直接在数据库上执行复杂的统计分析和数据挖掘,帮助决策过程,而无需数据传输的延迟。
4. 自动化报告和可视化 — Python 脚本可以直接从 SQL Server 数据生成数据可视化和报告,实现自动更新和仪表盘。
5. 机器学习模型的操作化 — 通过将 Python 集成到 SQL Server 中,模型可以直接在数据库环境中进行更新和管理,简化了操作工作流。
总之,虽然在 SQL Server 中执行 Python 存在一些挑战,但它也为在数据库环境中直接增强和简化数据处理、分析和预测建模开辟了丰富的可能性。
若要查看更多我的文章,可以在 LinkedIn 上关注我:
www.linkedin.com/in/sasha-korovkina-5b992019b/
SQL 用户定义函数(UDFs)
一篇关于掌握 SQL UDF 的教程:类别、用例及与存储过程的区别
·发表于 Towards Data Science ·9 分钟阅读·2024 年 8 月 22 日
--
图片来自 Rubaitul Azad 在 Unsplash
SQL 用户定义函数(UDF)是一个重要但常被忽视的特性。尽管有许多在线资源解释 SQL UDF 的语法,但其中大多数未能有效地指导用户如何在实际场景中应用此工具。因此,我写这篇文章来弥补这些空白,讨论何时以及如何使用 SQL UDF,探索其语法背后的基本逻辑,并提供实际的使用案例。此外,本文还将阐明各种 SQL UDF 的类型,并解决 UDF 与存储过程之间的混淆,后者是 SQL 中的另一项重要技术。考虑到 UDF 的语法在不同的数据库系统中可能有所不同,我将重点展示 SQL Server 中的 UDF,尽管我通常在工作中更喜欢使用 MySQL。选择这一示范的原因将在本文后续部分揭示。
什么是 UDF?何时应使用它?
在 SQL 中,用户定义函数(UDF)是开发者创建的对象,用于执行某个操作并返回结果。除了 UDF 外,SQL 还提供了内置函数(或本地函数)。SQL 预定义的内置函数包括字符串…
SQL 与计算器:从零开始构建冠军/挑战者测试
代码还是点击:哪种方法更适合 A/B 测试
深入的 SQL 代码,用于创建您自己的统计测试设计
·发布于Towards Data Science ·13 分钟阅读·2024 年 12 月 4 日
--
来自 Imagen 3 的图像
300 百万美元按钮:A/B 测试如何永远改变电子商务
我相信很多人都听过 300 百万美元按钮的故事。对于那些不知道这个故事的人来说,它讲述的是一个大型电子商务平台因顾客在结账时流失而错失了数百万美元的潜在收入。这个在线零售商曾有一个标注为“注册”的按钮,后来将其改为“继续”,并提供了稍后注册的选项,结果该公司年收入增加了 3 亿美元。这个案例研究由用户体验专家 Jared Spool 记录下来(来源:UIE,Jared Spool,“300 百万美元按钮”),展示了一个微小的变化如何显著影响商业结果。
然而,令人惊讶的是,尽管有报告显示 58%的高管在做出商业决策时仍依赖直觉,根据普华永道的报告(来源:普华永道全球数据与分析调查)。我一直认为,具有行业知识并精通商业流程的人,直觉固然重要,但在决策时结合数据和数字的实际证据能带来更多价值。冠军挑战者测试就是一种将猜测转化为科学验证的决策方法。
什么是冠军/挑战者测试?
冠军/挑战者测试(A/B 测试)是一种在企业中优化流程和业务运营的技术,通过选择最佳选项来提高业绩,增加收入,降低成本,并改善决策。冠军是当前最有效的操作或方法,而挑战者是你想与冠军进行对比的新的方法或策略,以查看它是否比当前的流程或策略更好或更差。你的冠军和挑战者应该有相同的设置,例如相似类型的账户或客户群体,以确保你做的是对比相同的条件。了解你想要实现的目标,并明确你的关键绩效指标(KPI),以衡量测试的成功是非常重要的。
通过 Oracle SQL 实现:实用指南
在实施冠军-挑战者测试时,我一直在想是否依赖在线计算器,还是投资于基于数据库的 SQL 实现。答案取决于多种因素,但让我们通过一个实际的示例来探讨 SQL 方法。在讲解示例的过程中,我还将向你介绍一些变量和条件的重要性,以确保我们创建一个扎实的冠军-挑战者测试。
想象一下一个催收机构想要测试留语音邮件与不留语音邮件的效果。当前策略不涉及语音邮件,一些人认为留下语音邮件可能改善联系率和支付率等指标,但在所有账户中实施此变更存在一些风险,比如可能会减少联系率、留下信息的合规性问题、留下语音邮件的资源成本以及支付率可能下降等。让我们设计一个严格的测试来评估这一假设。
为了开始我们的实施,我们需要创建一个结构化的基础,以跟踪我们的测试从开始到结束。我使用 Oracle SQL Developer 编写 SQL,并且为了在语音邮件测试上下文中的说明目的,我假设了如下提到的一些关键组件值,以生成语音邮件的冠军-挑战者测试。以下是这些关键组件的含义说明:
-
基线转化率:你正在测试的指标的当前转化率。在这个具体的语音邮件测试示例中,我们假设当前支付率为 8%,作为基线转化率。
-
最小可检测效应(MDE):你希望检测到的转化率的最小改善。例如,对于语音邮件,我们希望查看是否能将当前的转化率提高 10%,即提升至 8.8%(8% * (1 + 0.10) = 8.8%)。
-
统计显著性水平:通常设定为 95%,这意味着你有 95%的信心认为你的结果不是由偶然因素造成的。
-
统计功效:通常设定为 80%,这是一个衡量测试是否有足够数据以得出决定性结果的标准。
-
假设 / 尾部类型: 预测改变某个变量是否会影响客户行为的声明。有两种假设类型需要考虑,或者更多,被称为尾部检验:
a) 单尾检验: 仅当你在测试某事是否比当前表现更好或更差时,才推荐使用此检验。语音邮件测试中的单尾检验意味着我们只想知道语音邮件是否能改善支付率。
b) 双尾检验: 当你需要了解性能是否发生变化时,建议使用此检验。你在测试某件事是否比当前的表现更好或更差。语音邮件测试中的双尾检验意味着我们希望了解语音邮件是否会增加或减少支付率。
由于我们不知道语音邮件是否会增加或减少支付率,我们将使用双尾检验。
with test_parameters as(
select
0.08 as baseline_rate, -- assuming current rate of 8% of payment rate
10 as min_detectable_effect, -- wanting 10% improvement
95 as significance_level, -- 95% confidence level
80 as statistical_power, -- 80% statistical power
'TWO' as tail_type, -- 'ONE' or 'TWO' for tail type test
&volume as monthly_volume -- dynamic query to pull volume data can be used
-- example: (select count(*) from accounts where assign_date>=add_months(sysdate,-1) )
from dual
)
select * from test_parameters;
每月量输入的 SQL 提示
输出结果
上述配置很重要,因为它记录了我们正在测试的内容及其原因。这些指标是样本量计算的关键组件。我将向你展示样本量计算、分配比例、进行测试所需的月份和天数,最后是根据不同月度量的推荐结果。
样本量计算
使用正确的样本量非常重要,以确保测试结果在统计学上是显著的。样本量太小可能导致不准确的结果。更大的样本量会给你更准确的平均值,识别数据中的异常值并提供更小的误差范围。这里的关键问题是,什么样的样本量太小,什么样的样本量又太大。你会在文章的过程中找到答案。
以下 Oracle 脚本展示了如何计算样本量。我使用了 CTE 并将其分成多个快照部分,以便更好地解释代码。如果你想使用此脚本,需将所有代码部分合并。现在,我将设置我们的统计参数。
--statistical parameter conversion
,statistical_parameters as(
select
baseline_rate,
min_detectable_effect,
monthly_volume,
tail_type,
--set confidence level z-score based on tail type
case when tail_type='ONE' then
case significance_level
when 90 then 1.28 -- One tailed test for 90% confidence
when 95 then 1.645 -- One tailed test for 95% confidence
when 99 then 2.326 -- One tailed test for 99% confidence
else 1.645 end
else
case significance_level
when 90 then 1.645 -- Two tailed test for 90% confidence
when 95 then 1.96 -- Two tailed test for 95% confidence
when 99 then 2.576 -- Two tailed test for 99% confidence
else 1.96 end end as z_alpha,
--set power level z-score (same for both tail types)
case statistical_power
when 80 then 0.84
when 90 then 1.28
when 95 then 1.645
else 0.84 end as z_beta
from test_parameters
)
select * from statistical_parameters;
该转换将置信水平转换为用于样本量计算的统计值。对于催收,95%的置信度意味着有 5%的可能性结果是错误的,或者在语音邮件无效时。
在统计学中,z-alpha 代表我们的置信水平,具体值取决于置信水平和尾部类型测试。通常,双尾检验的值比单尾检验的值要高,因为双尾检验的错误率被分配到两个方向。对于语音邮件测试场景,5%的错误概率表示错误率在两个方向均匀分配(支付下降的概率为 0.025,支付上升的概率为 0.025),而单尾检验将 0.05 的概率集中在一个方向,因为我们只关心支付是上升还是下降,而不是两者都关心。
统计能力称为 z-beta。当我们设置 80%的统计能力(z-beta = 0.84)时,意味着我们希望在 80%的情况下捕捉到真实变化,并且接受在 20%的情况下错过它们。
Z-alpha 和 Z-beta 结合意味着,如果语音邮件确实有助于提高付款率,我们将在 80%的情况下发现这一改善,当我们发现时,我们可以 95%确信它是真正的改进,而不是偶然的结果。
输出结果
现在让我们进入所需样本量的计算。这个计算决定了我们需要测试多少账户。在我们的语音邮件场景中,如果我们希望将付款率从 8%提升至 8.8%,那么这个计算将告诉我们需要多少账户来确保付款率的提升或下降是实际存在的,而不是偶然的。
--Sample size calculation
,sample_size_calculation as(
select
baseline_rate,
min_detectable_effect,
monthly_volume,
tail_type,
z_alpha,
z_beta,
--calculate minimum effect size
baseline_rate*(min_detectable_effect/100) as minimum_effect,
--calculate base sample size
ceil(
case tail_type
when 'ONE' then
( power(z_alpha + z_beta, 2) * baseline_Rate * (1 - baseline_Rate)) / (power(baseline_Rate * (min_detectable_effect/100), 2))
else
(2 * power(z_alpha + z_beta, 2) * baseline_Rate * (1 - baseline_Rate)) / (power(baseline_Rate * (min_detectable_effect/100), 2))
end
) as required_sample_size
from statistical_parameters
)
输出结果
分割比例和测试持续时间
分割比例决定了你如何在冠军组(当前版本)和挑战者组(测试版本)之间划分数据集。常见的分割比例包括二分法(如 50/50、80/20 或 90/10 分割)或多分法(如 50/25/25 或 70/10/10/10)。这些多分法测试用于测试不同的变化,同时保留一个对照组。
选择分割比例不应是随意的,或者仅仅依赖于可用的样本量,还应考虑其他因素,如对挑战者版本的信心水平、变化的影响,特别是如果这种变化会影响当前的指标,并确保测试满足所需的最小样本量要求。
以下分析将统计要求转化为业务术语,并展示不同分割比例如何影响测试持续时间。它还展示了基于分割比例的风险水平。分割比例表示我们如何在冠军组和挑战者组之间划分账户。
--split ratio
,split_ratios as(
--generate split ratios from 10 to 50 for challenger
Select
level * 10 as challenger_pct,
100 - (level * 10) as control_pct
from dual
connect by level <= 5 -- This generates 10/90, 20/80, 30/70, 40/60, 50/50
)
--split_analysis
,split_analysis as(
select
s.baseline_Rate * 100 as current_rate_pct,
s.baseline_rate * (1 + s.min_detectable_effect/100) * 100 as target_rate_pct,
s.min_detectable_effect as improvement_pct,
s.tail_type,
s.required_sample_size as sample_size_per_group,
s.required_sample_size * 2 as total_sample_needed,
s.monthly_volume,
r.challenger_pct,
r.control_pct,
--calculate test duration (months) for different splits
round(s.required_sample_size / (s.monthly_volume * (r.challenger_pct/100)), 1) as months_needed,
--calculate test days needed for each split
round(s.required_sample_size / (s.monthly_volume * (r.challenger_pct/100)) * 30, 0) as days_needed,
--Assess risk level for each split
case
when r.challenger_pct <= 20 then 'Conservative'
when r.challenger_pct <= 35 then 'Balanced'
else 'Aggressive' end as risk_level
from sample_size_calculation s cross join split_ratios r
)
select * from split_analysis;
保守风险只会影响 10-20%的账户接受新处理,80-90%的账户则受到潜在负面影响。这个分配比例需要更长时间来收集足够的数据。平衡风险将影响三分之一的账户,同时保护其余账户,并且能够更快收集数据。激进风险虽然能够快速收集数据,但会影响多达一半的账户,因此会暴露更多账户于风险之中。
输出结果的一部分
了解冠军/挑战者测试应该运行多久非常重要。如果测试运行时间过短,可能会因为数据不完整或误导性信息做出错误决策;如果测试运行时间过长,则可能浪费资源并延迟决策。为了保持平衡,通常测试应该至少运行一个完整的业务周期。测试通常不应超过 4 到 8 周,这样可以避免将结果与其他运营或季节性变化混淆。
风险评估和数量需求
我观察到新接触冠军/挑战者测试的分析师不知道选择哪个拆分比例。我们可以通过考虑选择某个拆分比例的风险以及该拆分比例所需的数量来决定选择哪个拆分比例。
必须计算最坏情况,以评估风险水平。
,risk_Assessment as(
select
monthly_volume,
sample_size_per_group,
challenger_pct,
risk_level,
--assess potential impact
round(monthly_volume * (challenger_pct/100) * (current_rate_pct/100)) as accounts_at_risk,
round(monthly_volume * (challenger_pct/100) * (current_rate_pct/100) * (1 - (improvement_pct/100))) as worst_case_scenario
from split_analysis
)
,volume_recommendations as(
select distinct
sample_size_per_group,
--recommende monthly volumes for different completion timeframes for all splits
ceil(sample_size_per_group / 0.5) as volume_for_1_month_50_50, --50/50 split
ceil(sample_size_per_group / 0.4) as volume_for_1_month_40_60, --40/60 split
ceil(sample_size_per_group / 0.3) as volume_for_1_month_30_70, --30/70 split
ceil(sample_size_per_group / 0.2) as volume_for_1_month_20_80, --20/80 split
ceil(sample_size_per_group / 0.1) as volume_for_1_month_10_90 --10/90 split
from split_analysis
)
部分输出结果
假设我们选择 30/70 拆分比例,这对于语音邮件显示出一种“平衡”的拆分。每月有 10,000 个账户,其中 3,000 个账户将接收语音邮件,而 7,000 个账户将继续正常运行。如果语音邮件表现不佳,将影响 3,000 个账户,最大暴露的风险是 240 笔付款(3,000 * 8%)。在这种情况下,假设语音邮件测试使付款率下降了 10%而不是提高,我们将只收到 216 笔付款(3,000 * 8% * (1–10%))。这意味着我们失去了本可以收到的 24 笔付款。
这个最坏情况的计算帮助我们了解所面临的风险。在更激进的 50/50 拆分下,测试组中将有 5,000 个账户,面临在最坏情况下可能失去 40 笔付款的风险。保守的 20/80 拆分则只会冒 16 笔付款的风险,尽管测试完成的时间会更长。
在 50/50 拆分的情况下,我们需要总共 36,000 个账户,以便在测试组中获得 18,000 个账户。由于我们每月只有 10,000 个账户,这意味着我们的测试大约需要 3.6 个月才能完成。转向最保守的 10/90 拆分则需要 180,000 个账户,使得测试周期达到不切实际的 18 个月。
,final_Recommendation as(
select
sa.*,
ra.accounts_At_Risk,
ra.worst_case_scenario,
vr.volume_for_1_month_50_50,
vr.volume_for_1_month_40_60,
vr.volume_for_1_month_30_70,
vr.volume_for_1_month_20_80,
vr.volume_for_1_month_10_90,
--Generate final recommendations based on all split ratios
case when sa.monthly_volume >= vr.volume_for_1_month_50_50 and sa.challenger_pct = 50
then 'AGGRESSIVE: 50/50 split possible. Fastest completion in ' || sa.days_needed || ' days but highest risk '
when sa.monthly_volume >= vr.volume_for_1_month_40_60 and sa.challenger_pct = 40
then 'MODERATELY AGGRESSIVE: 40/60 split feasible. Completes in ' || sa.days_needed || ' days with moderate-high risk.'
when sa.monthly_volume >= vr.volume_for_1_month_30_70 and sa.challenger_pct = 30
then 'BALANCED: 30/70 split recommended. Completes in ' || sa.days_needed || ' days with balanced risk.'
when sa.monthly_volume >= vr.volume_for_1_month_20_80 and sa.challenger_pct = 20
then 'CONSERVATIVE: 20/80 split possible. Takes ' || sa.days_needed || ' days with lower risk.'
when sa.monthly_volume >= vr.volume_for_1_month_10_90 and sa.challenger_pct = 10
then 'BALANCED: 10/90 split possible. Takes ' || sa.days_needed || ' days but minimizes risk.'
else 'NOT RECOMMENDED: Current volume of ' || sa.monthly_volume || ' insufficient for reliable testing with '
|| sa.challenger_pct || '/' || sa.control_pct || ' split.' end as recommendation
from split_analysis sa join risk_assessment ra on sa.challenger_pct=ra.challenger_pct
cross join volume_recommendations vr
)
select
tail_type as test_type,
current_rate_pct || '%' as current_rate,
target_rate_pct || '%' as target_rate,
improvement_pct || '%' as improvement,
sample_size_per_group as needed_per_group,
total_sample_needed as total_needed,
monthly_volume,
challenger_pct || '/' || control_pct || ' split' as split_ratio,
days_needed || ' days (' || round(months_needed, 1) || ' months)' as duration,
risk_level,
accounts_At_Risk || ' accounts at risk' as risk_exposure,
worst_Case_Scenario || ' worst case' as risk_scenario,
case
when challenger_pct = 10 then
case
when monthly_volume >= volume_for_1_month_10_90
then 'Current volume (' || monthly_volume || ') sufficient for 10/90 split'
else 'Need ' || volume_for_1_month_10_90
|| ' monthly accounts for 10/90 split (current: ' || monthly_volume || ')'
end
when challenger_pct = 20 then
case
when monthly_volume >= volume_for_1_month_20_80
then 'Current volume (' || monthly_volume || ') sufficient for 20/80 split'
else 'Need ' || volume_for_1_month_20_80
|| ' monthly accounts for 20/80 split (current: ' || monthly_volume || ')'
end
when challenger_pct = 30 then
case
when monthly_volume >= volume_for_1_month_30_70
then 'Current volume (' || monthly_volume || ') sufficient for 30/70 split'
else 'Need ' || volume_for_1_month_30_70
|| ' monthly accounts for 30/70 split (current: ' || monthly_volume || ')'
end
when challenger_pct = 40 then
case
when monthly_volume >= volume_for_1_month_40_60
then 'Current volume (' || monthly_volume || ') sufficient for 40/60 split'
else 'Need ' || volume_for_1_month_40_60
|| ' monthly accounts for 40/60 split (current: ' || monthly_volume || ')'
end
else
case
when monthly_volume >= volume_for_1_month_50_50
then 'Current volume (' || monthly_volume || ') sufficient for 50/50 split'
else 'Need ' || volume_for_1_month_50_50
|| ' monthly accounts for 50/50 split (current: ' || monthly_volume || ')'
end
end as volume_assessment,
recommendation
from final_Recommendation
order by challenger_pct;
每月 10,000 个账户的部分输出结果
如果每月量为 50,000 个账户:
每月 50,000 个账户的部分输出结果
某些问题需要考虑,以决定选择哪个拆分比例和可接受的风险水平,并最终了解可用于测试语音邮件的数量。业务是否能接受每月可能失去 40 笔付款,以换取 3.6 个月内完成测试,还是选择每月仅失去 16 笔付款,但延长测试时间会更好?通过仔细选择拆分比例并理解适当的样本量,你可以设计出提供准确且可操作见解的测试。
计算器与 SQL 实现
像Evan Miller和Optimizely这样的在线计算器是非常有价值的工具,通常默认采用 50/50 的分配比例或双尾测试。另一个在线工具,Statsig,没有默认设置,但同时也不会提供像我们通过 SQL 实现所编码的额外细节。SQL 实现的价值在于,它不仅帮助跟踪基本指标,还能够根据你的实际月度量监控风险暴露和测试持续时间。当你需要偏离标准的 50/50 分配,或者想要了解测试设计中的不同分配比例和商业风险时,这种全面的视角尤其重要。
持续测试
冠军/挑战者测试不是一次性的工作,而是一个持续改进的周期。创建性能报告并持续监控结果。适应不断变化的条件,包括季节性变化和经济变化。通过将这种方法整合到你的策略测试中,你正在创建一种系统化的决策方法,推动创新,降低风险,最重要的是,你的直觉可以通过可靠的数据证据来支持。
注意:除非另有说明,否则所有图片均为作者所提供。
SQLite 在现代网页生产中的应用:梦想变为现实
关于激进简化的美德
·发表于 Towards Data Science ·10 分钟阅读·2024 年 12 月 12 日
--
简单的风景。图片来源:Unsplash。
这是关于使用 SQLite 进行网页应用和机器学习的两篇系列文章中的第一篇。在本文中,我将深入探讨为什么 SQLite 正在迅速成为现代网页应用的生产就绪数据库。在第二篇文章中,我将讨论如何使用 SQLite 进行检索增强生成(RAG)。
如果您想要一个定制的网页应用程序,并且集成了生成型 AI,请访问 losangelesaiapps.com
SQLite:逃离复杂性的洞穴
柏拉图的洞穴寓言,由 Jan Saenredam 创作,1604 年。
“如果你追求宁静,就少做事。”
— 马库斯·奥勒留
当今运行现代网页软件的大多数数据库都采用客户端-服务器架构。在这种架构中,服务器是管理数据的中央系统。它处理来自客户端的请求并向客户端发送响应。这里的客户端指的是通过服务器与数据库交互的用户或应用程序。
客户端-服务器架构。图片来源:pixabay。
理解这一架构的简单方法是通过类比图书馆。服务器是图书馆,每一条数据就是一本书,而客户端是访客。在这个世界中,访客不能直接从书架上拿书。他们必须通过图书管理员,图书管理员精心整理了图书馆,以便访客能够轻松找到所需的书。在这个世界里,访客进入图书馆的过程完全通过图书馆的工作人员(服务器端)来媒介化。
这是一种相当巧妙的架构。然而,对于较小、轻量级的应用程序来说,这种做法过于复杂。如果你只有几本书,为什么需要建立多个书架,更不用说多个房间了?与客户端-服务器架构相对的,是SQLite 数据库使用的单文件架构。
对于初学者来说,SQLite 是数据库的柏拉图式理想。与运行整个服务器来管理数据访问不同,这个数据库完全存储在一个单一文件中。然后,你的应用程序只需通过修改这个文件来创建、读取、更新和销毁数据。当你部署一个由客户端-服务器数据库支持的 Web 应用程序时,你实际上是在部署两个服务:一个是你的应用程序,另一个是你的数据库。而使用 SQLite 时,你只需部署一个服务:包含 SQLite 文件的应用程序。这意味着更少的复杂性和更低的成本。
回到我们的类比,使用 SQLite 就像拥有一本存储所有数据的单一笔记本。没有书架,没有图书馆,没有图书管理员。你只需打开书本,添加、删除或更新你的数据。也许你可以使其更复杂些,在书本的后面加一个索引来加速搜索。你可以想象这会变得多么简单。
然而,正如经济学中所说的:没有解决方案,只有权衡。SQLite 并不完美,现代 Web 应用中很少使用它有其合理的原因。在本文中,我将重点讨论一些困扰 SQLite 的问题,以及近年来的进展如何消除了这些障碍。
问题 #1:并发
SQLite 的主要问题通常与并发性相关。SQLite 使用写锁来确保一次只有一个写操作进行。我们不希望事务相互干扰。如果你尝试发送并发写请求,你通常会遇到SQLITE_BUSY
错误,并且其中一个事务会丢失。在并发请求的情况下,我们希望事务排队并彼此友好地运行。
不幸的是,SQLite 中的默认事务模式并不促进这一点。一些重要的背景信息:事务通常涉及一系列数据库语句,例如读取和写入,它们一起执行。
-- An example transaction
BEGIN DEFERRED TRANSACTION;
SELECT * FROM inventory WHERE id = 1; -- Statement 1
UPDATE inventory SET stock = stock + 1 WHERE id = 1; -- Statement 2
SQLite 中的默认事务模式是延迟事务模式。在此模式下:
-
事务开始时不会获取任何锁。
-
只读语句不会触发写锁;它只需要一个共享的读锁,这允许并发读取。想想
SELECT
语句。 -
写语句需要一个独占的写锁,这会阻止所有其他读取和写入,直到事务完成。想想
INSERT
、UPDATE
或DELETE
语句。
例如,来看一下以下两笔交易。假设它们同时运行:
-- Transaction 1
BEGIN DEFERRED TRANSACTION;
SELECT * FROM inventory WHERE id = 1;
UPDATE inventory SET stock = stock + 1 WHERE id = 1;
-- Transcation 2
BEGIN DEFERRED TRANSACTION;
UPDATE inventory SET stock = stock - 1 WHERE id = 1;
-- Example sequence of events:
-- Transaction 1 begins
-- SELECT statement: No lock is acquired yet.
-- Transaction 2 begins
-- Acquires a write lock (UPDATE statement).
-- Transcation 1 continues
-- Tries to acquire a write lock (UPDATE statement).
-- Fails because Transaction 2 already committed and released the lock.
-- SQLite throws SQLITE_BUSY.
-- Transaction 2 commits successfully. Transaction 1 has failed.
在这种情况下,由于事务 1
在抛出SQLITE_BUSY
异常时正处于事务中,它不会在事务 2
完成写锁后重新排队,而是会被取消。SQLite 不想冒险出现不一致的结果,万一另一个事务在锁等待期间修改了重叠的数据,所以它只会告诉被中断的事务放弃。
这样想:假设你和你的朋友正在共享一本笔记本。你开始阅读笔记本里的一篇未完成的故事,打算写下下一部分。但在你准备拿起笔之前,你的朋友抢走了笔记本。“反正你也没写什么!”他们大声说道。如果他们在你的故事中改变了什么关键内容怎么办?你感到沮丧,无法继续下去,愤怒地放弃了,放弃了完成这个故事的尝试。结果发现,你的朋友并不像你想象的那么友好!
我们如何解决这个问题呢?假设你们规定一个规则:当你们中的任何一个人拿到笔记本时,无论是读还是写,那个人都可以使用笔记本直到完成?问题解决!
SQLite 中的这种事务模式被称为即时模式。现在,当一个事务开始时,无论它是写操作还是读操作,它都会占用写锁。如果一个并发事务试图占用写锁,它会排队等待当前事务完成,而不会抛出SQLITE_BUSY
错误。
使用即时事务模式可以大大缓解 SQLite 中的并发问题。为了进一步提高并发性,我们还可以更改日志模式。默认的模式是回滚日志。在这种模式下,数据库页面的原始内容会在修改之前被复制。这样,如果事务失败或者你愿意,你总是可以回到日志中恢复数据库到其原始状态。这对于可重复性非常有用,但对并发性不利。复制整个数据库页面的操作非常慢,并且会占用写锁,延迟任何读操作。
为了解决这个问题,我们可以改用预写日志(WAL)。不同于直接将更改写入主数据库文件,首先会将更改记录在一个单独的日志文件(即“预写日志”)中,然后按一定间隔将其应用到数据库中。读取者仍然可以访问最近提交的写操作,因为 SQLite 在读取时不仅会检查主数据库文件,还会检查 WAL 文件。这将写操作和读操作分离开,缓解了由于扩展所带来的并发问题。
为了继续我们的类比,写前日志就像每次对共享笔记本进行更改时,都需要抓取一张便签。如果有人想查看笔记本中的某个部分,他们可以检查该部分是否附有便签,以获取最新的更新。通过这种方法,你可以让很多人同时阅读同一本笔记本。当便签积累到一定程度时,你就可以开始编辑笔记本本身,编辑完成后可以丢掉这些便签。
这些 SQLite 中的配置选项已经存在了几十年(写前日志在 2010 年引入)。既然如此,为什么 SQLite 在生产环境中已经使用了几十年?这就引出了我们下一个问题。
问题 #2:慢速硬件
与固态硬盘(SSD)相比,硬盘驱动器(HDD)在多种对数据库管理至关重要的操作上表现得非常慢。例如,在延迟(单次 I/O 操作所需的时间)方面,SSD 比 HDD 快大约 100 倍。在随机 I/O 操作每秒(IOPS)方面,SSD 比 HDD 快约 50 至 1000 倍。SSD 之所以比 HDD 快得多,是因为 SSD 没有运动部件。HDD 使用旋转的磁盘和运动部件来读取和写入数据,类似于老式的唱盘,而 SSD 仅使用电子组件,类似于一个巨大的 USB 闪存盘。
尽管其性能较差,HDD(硬盘驱动器)历史上主要因低成本而主导了存储市场。然而,SSD(固态硬盘)迅速迎头赶上。2011 年,SSD 的每 GB 价格大约是 HDD 的 32 倍(来源)。到 2023 年,价格差距缩小,SSD 的每 GB 价格大约是 HDD 的 3 到 5 倍(来源)。在过去的一年里,由于三星等制造商的价格削减以及数据中心需求的增加,SSD 的价格有所上升。然而,从长远来看,我们可以预期 SSD 的价格会继续下降。即使与 HDD 的价格永远无法持平,SSD 的低绝对价格足以确保其广泛采用。在 2020 年,SSD 的销量超过了 HDD,出货量达到 3.33 亿台,而 HDD 为 2.6 亿台,这标志着存储市场的一个转折点(来源)。
截至 2024 年 12 月,你可以在像Hetzner这样的服务上租用一台配备 80GB SSD 存储的专用 vCPU,每月约需 16 美元。240GB 的存储大约需要 61 美元。你还可以通过共享 vCPU 获得更便宜的价格。对于许多较小的应用程序来说,这种存储空间绰绰有余。便宜的 SSD 的使用解决了在生产级 Web 应用中使用 SQLite 时的一个重要瓶颈。但仍然有一个更重要的问题需要处理。
问题 #3:备份
不言而喻,在生产环境中为数据库创建备份至关重要。任何创业公司最不希望发生的事情就是其主数据库损坏,导致所有用户数据丢失。
创建备份的第一个选项是最简单的。由于 SQLite 数据库仅仅是一个文件,你可以本质上将数据库复制并粘贴到计算机上的一个文件夹中,或者将其上传到 AWS S3 桶等云服务中以提高可靠性。对于小型数据库以及写操作不频繁的情况,这是一个很好的选择。以下是一个简单的示例(取自Litestream 文档),展示了一个用于创建备份的 bash 脚本:
#!/bin/bash
# Ensure script stops when commands fail.
set -e
# Backup our database to the temp directory.
sqlite3 /path/to/db "VACUUM INTO '/path/to/backup'"
# Compress the backup file for more efficient storage
gzip /tmp/db
# Upload backup to S3 using a rolling daily naming scheme.
aws s3 cp /tmp/db.gz s3://mybucket/db-`date +%d`.gz
几个注意事项:
-
set -e
中的-e
选项表示“立即退出”。这确保了如果任何命令失败,脚本将会停止执行。 -
SQLite 的
VACUUM INTO
命令会创建 SQLite 数据库的紧凑备份。它减少了数据库中的碎片化,并且减小了文件大小。可以把它当作数据库的整洁版。不过,你不一定要使用VACUUM INTO
;你也可以用.backup
来替代。这会将整个数据库文件,包括所有数据和结构,原样复制到另一个文件中。 -
SQLite 数据库压缩效果很好,
gzip
命令可以方便地实现这一点。 -
最后,你可以将文件副本上传到你选择的云存储提供商。在这里,我们将文件上传到 S3。
如果你希望备份任务自动运行,可以配置crontab
以定期执行此任务。这里我们每天午夜运行脚本:
# Edit your cron jobs
crontab -e
# Add this to the end of the crontab
0 0 * * * /path/to/my_backup_script.sh
对于写操作频繁的数据库,如果你想要在任何给定时刻捕获数据库的状态,可以使用Litestream。这是一个开源工具,旨在通过将更改流式传输到远程存储后端,为 SQLite 数据库提供实时复制。
Litestream 能够跟踪 SQLite 的 WAL 文件的变化。还记得便签纸吗?每当新的事务记录到 WAL 文件时,Litestream 就能将这些增量数据复制到你选择的云存储提供商。这使得我们能够保持数据库的近实时备份,而无需每次都创建完整的副本。
要开始使用 Litestream,首先需要安装它。在 MacOS 上,您可以使用 Homebrew 进行安装。然后,你需要设置一个litestream.yml
配置文件:
# /etc/litestream.yml
dbs:
- path: /path/to/your.db
replicas:
- type: s3
bucket: your-s3-bucket-name
path: your-database-name
region: your-region
这里,我们将向 S3 桶流式传输数据库事务。然后,我们可以运行以下命令开始复制:
litestream replicate -config /etc/litestream.yml
在这种情况下,我们设置将your.db
中的任何事务复制到 S3 桶中。就这样!然后,你就可以通过回放 WAL 的更改将 SQLite 数据库恢复到任何先前的状态。例如,如果你想从 2024 年 12 月 10 日 UTC 时间 15:00 的时间戳创建一个名为restored.db
的数据库副本,你可以运行以下命令:
litestream restore -o /path/to/restored.db \
-timestamp "2024-12-10T15:00:00Z" \
s3://your-s3-bucket-name/your-database-name
要获取数据库的最新版本备份,只需省略-timestamp
标志。
结论
我鼓励你观看最近的 Rails World 2024 讲座,了解 SQLite 如何快速变得适合现代 web 应用的生产环境。他们已经将我们在这里讨论的一些改进应用到了他们的 SQLite 适配器中。如果你想深入了解,我还推荐阅读 Stephen Margheim 的文章,详细介绍了他在 Rails 中对 SQLite 的工作。你最好相信,这些改进很快也会应用到 Django、Laravel 等框架中。
SQLite 在生产环境中的改进尚未完成。Rails 的创造者 David Heinemeier Hansson 希望推动 SQLite,使其能够支撑一个中型 SaaS 公司运营。令人激动的时刻!
压缩平均值:深入探讨 Python 中的惩罚分位回归
如何构建惩罚分位回归模型(附代码!)
·发表于 Towards Data Science ·5 分钟阅读·2024 年 8 月 16 日
--
图片由 Joes Valentine / Unsplash 提供:想象这些是正态分布。
这是我关于惩罚回归系列的第三篇文章。在第一篇中,我们讨论了如何在 Python 中实现 稀疏组套索,这是目前回归模型中最好的变量选择方法之一;第二篇我们讨论了 自适应估计量,以及它们如何优于传统的估计方法。今天,我想讨论一下分位回归,并深入探讨如何使用稳健的 **asgl**
包进行高维分位回归,重点介绍带有自适应套索惩罚的分位回归实现。
今天我们将会看到:
-
什么是分位回归?
-
与传统的最小二乘回归相比,分位回归有哪些优势?
-
如何在 Python 中实现惩罚分位回归模型
什么是分位回归?
让我们从许多人可能已经遇到过的东西开始:最小二乘回归。当我们希望基于一些输入变量预测结果时,这通常是经典的首选方法。它……
使用哈希空间实现稳定且快速的随机化
在不同的实施环境中动态生成一致的任务分配
·发表于 Towards Data Science ·阅读时间 8 分钟·2024 年 7 月 29 日
--
鸟瞰图
进行实验的核心部分是将一个实验单位(例如一个客户)分配到特定的处理(支付按钮变体、营销推送通知框架)。通常,这种分配需要满足以下条件:
-
它需要是随机的。
-
它需要是稳定的。如果客户返回到该页面,他们需要看到相同的支付按钮变体。
-
它需要在实际分配后能够快速检索或生成,以便在后续分析中使用。
-
它需要在实际分配后仍然可用,以便在后续分析中使用。
当组织开始进行实验时,一个常见的做法是预先生成分配任务,将其存储在数据库中,然后在分配时提取出来。这是一个完全有效的方法,对于刚开始的实验非常有效。然而,随着客户和实验量的增加,这种方法变得越来越难以维护,并且难以可靠使用。你必须 (a) 管理存储的复杂性,(b) 确保任务在实验内和实验间都是随机的,并且 (c) 确保任务能被可靠地提取。所有这些在大规模实验中都很困难。
使用哈希空间有助于解决这些问题。这是一个简单的解决方案,但可能没有得到应有的关注。这个博客尝试解释这种技术。文末有不同编程语言的代码链接。不过,如果你愿意的话,也可以直接跳转到代码。
设置
我们正在进行一个实验,测试我们客户应用中哪种进度条变体能够带来最多的用户参与度。有三个变体:控制组(默认体验)、变体 A 和变体 B。
我们有 1000 万客户每周使用我们的应用,并且我们希望确保这 1000 万客户会随机分配到三个变体中的一个。每次客户回到应用时,他们应该看到相同的变体。我们希望控制组的分配概率为 50%,变体 1 的分配概率为 30%,变体 2 的分配概率为 20%。
probability_assignments = {"Control": 50, "Variant 1": 30, "Variant 2": 20}
为了简化问题,我们从 4 个客户开始。这些客户有我们用来引用他们的 ID。这些 ID 通常是 GUID(类似于“b7be65e3-c616-4a56-b90a-e546728a6640
”)或整数(如 1019222,1028333)。这些 ID 类型都可以使用,但为了便于理解,我们假设这些 ID 为:“Customer1”,“Customer2”,“Customer3”,“Customer4”。
我们的目标是将这四个客户映射到三种可能的变体上。
哈希客户 ID
该方法主要依赖于使用具有一些非常理想属性的哈希算法。哈希算法将任意长度的字符串映射到固定长度的“哈希”值。理解这一点最简单的方法是通过一些例子。
哈希函数接受一个字符串,并将其映射到一个常量哈希空间中。在下面的示例中,哈希函数(在本例中是md5)将以下单词:“Hello”,“World”,“Hello World”和“Hello WorLd”(注意大写的 L)映射为一个由 32 个字符组成的字母数字字符串。
有几点重要的事项需要注意:
-
所有的哈希值长度相同。
-
输入中的一个小差异(大写的 L 而不是小写的 l)会改变哈希值。
-
哈希值是一个十六进制字符串。也就是说,它由数字 0 到 9 以及前六个字母(a、b、c、d、e 和 f)组成。
我们可以使用相同的逻辑来计算四个客户的哈希值:
import hashlib
representative_customers = ["Customer1", "Customer2", "Customer3", "Customer4"]
def get_hash(customer_id):
hash_object = hashlib.md5(customer_id.encode())
return hash_object.hexdigest()
{customer: get_hash(customer) for customer in representative_customers}
# {'Customer1': 'becfb907888c8d48f8328dba7edf6969',
# 'Customer2': '0b0216b290922f789dd3efd0926d898e',
# 'Customer3': '2c988de9d49d47c78f9f1588a1f99934',
# 'Customer4': 'b7ca9bb43a9387d6f16cd7b93a7e5fb0'}
十六进制字符串只是数字在 16 进制表示中的形式。我们可以将它们转换为十进制整数。
⚠️ 这里有一个重要的注意事项:我们很少需要使用完整的哈希值。实际上(例如在链接的代码中),我们只使用哈希的一个较小部分(前 10 个字符)。这里我们使用完整的哈希值是为了使解释更容易一些。
def get_integer_representation_of_hash(customer_id):
hash_value = get_hash(customer_id)
return int(hash_value, 16)
{
customer: get_integer_representation_of_hash(customer)
for customer in representative_customers
}
# {'Customer1': 253631877491484416479881095850175195497,
# 'Customer2': 14632352907717920893144463783570016654,
# 'Customer3': 59278139282750535321500601860939684148,
# 'Customer4': 244300725246749942648452631253508579248}
这些整数有两个重要的属性:
-
这些整数是稳定的:给定一个固定的输入(“Customer1”),哈希算法总是会产生相同的输出。
-
这些整数是均匀分布的:这个概念还没有解释,主要应用于加密哈希函数(如 md5)。均匀性是这些哈希函数的设计要求。如果它们不是均匀分布的,那么碰撞的几率(不同输入得到相同输出的情况)会更高,从而削弱哈希的安全性。有一些探讨讨论了均匀性属性。
现在我们有了每个 ID 的整数表示,它是稳定的(始终具有相同的值)且均匀分布,我们可以使用它来进行分配。
从整数表示到分配
回到我们的概率分配,我们希望将客户分配到变体的分布如下:
{"Control": 50, "Variant 1": 30, "Variant 2": 20}
如果我们有 100 个位置,我们可以将它们分成 3 个桶,每个桶中的位置数量表示我们希望分配给该桶的概率。例如,在我们的例子中,我们将整数范围 0–99(100 个单位)分为 0–49(50 个单位)、50–79(30 个单位)和 80–99(20 个单位)。
def divide_space_into_partitions(prob_distribution):
partition_ranges = []
start = 0
for partition in prob_distribution:
partition_ranges.append((start, start + partition))
start += partition
return partition_ranges
divide_space_into_partitions(prob_distribution=probability_assignments.values())
# note that this is zero indexed, lower bound inclusive and upper bound exclusive
# [(0, 50), (50, 80), (80, 100)]
现在,如果我们将一个客户随机分配到 100 个位置之一,那么最终的分布应该与我们期望的分布相同。另一种思考方式是,如果我们在 0 到 99 之间随机选择一个数字,那么有 50%的概率它会在 0 到 49 之间,30%的概率它会在 50 到 79 之间,20%的概率它会在 80 到 99 之间。
唯一剩下的步骤是将我们生成的客户整数映射到这些 100 个位置之一。我们通过提取生成整数的最后两位数字并将其作为分配来完成此操作。例如,客户 1 的最后两位数字是 97(你可以查看下面的图表)。这属于第三个桶(变体 2),因此该客户被分配到变体 2。
我们对每个客户重复这一过程,直到所有客户处理完毕。完成后,我们应该发现最终的分布符合我们的预期:50%的客户在控制组,30%在变体 1 组,20%在变体 2 组。
def assign_groups(customer_id, partitions):
hash_value = get_relevant_place_value(customer_id, 100)
for idx, (start, end) in enumerate(partitions):
if start <= hash_value < end:
return idx
return None
partitions = divide_space_into_partitions(
prob_distribution=probability_assignments.values()
)
groups = {
customer: list(probability_assignments.keys())[assign_groups(customer, partitions)]
for customer in representative_customers
}
# output
# {'Customer1': 'Variant 2',
# 'Customer2': 'Variant 1',
# 'Customer3': 'Control',
# 'Customer4': 'Control'}
链接的摘要中有一个针对 1,000,000 个客户的复现,我们可以观察到客户分布符合预期的比例。
# resulting proportions from a simulation on 1 million customers.
{'Variant 1': 0.299799, 'Variant 2': 0.199512, 'Control': 0.500689
实际考虑
在实验中添加“盐”
实际上,当你在不同部分的产品中运行多个实验时,通常会在哈希 ID 之前添加一个“盐”。这个盐可以是任何东西:实验名称、实验 ID、功能名称等。这确保了在实验之间客户到桶的映射始终不同,因为盐值不同。这对于确保客户不会总是落入相同的桶中非常有帮助。例如,你不希望特定客户总是落入对照组处理桶,如果恰好是对照组分配了所有实验中的前 50 个桶。这个实现起来很简单。
salt_id = "f7d1b7e4-3f1d-4b7b-8f3d-3f1d4b7b8f3d"
customer_with_salt_id = [
f"{customer}{salt_id}" for customer in representative_customers
]
# ['Customer1f7d1b7e4-3f1d-4b7b-8f3d-3f1d4b7b8f3d',
# 'Customer2f7d1b7e4-3f1d-4b7b-8f3d-3f1d4b7b8f3d',
# 'Customer3f7d1b7e4-3f1d-4b7b-8f3d-3f1d4b7b8f3d',
# 'Customer4f7d1b7e4-3f1d-4b7b-8f3d-3f1d4b7b8f3d']
增加分区空间
在这个例子中,我们使用了一个包含 100 个可能槽位(或分区)的空间。如果你想分配具有一个或多个小数位的概率,你只需取整数的最后 n 位数字。
例如,如果你想为概率分配最多两位小数,你可以取整数的最后 4 位数字。也就是说,下面的place_value
值将是 10000。
def get_relevant_place_value(customer_id, place_value):
hash_value = get_integer_representation_of_hash(customer_id)
return hash_value % place_value
跨环境可用性
最后,这个方法之所以在实践中如此强大和有吸引力的一个原因是,你可以在不同的实现环境中精确复制这个过程。只要输入相同,核心哈希算法在任何环境中都会给你相同的哈希值。你需要做的只是CustomerId
、SaltId
和预期的概率分布。你可以在这里找到不同的实现:
总结
如果你想在不同的实现环境中生成随机、快速且稳定的分配,你可以使用以下方法:
-
使用加密哈希函数(如 md5)从唯一 ID 和一些“盐”生成哈希值。
-
将其转换为十进制整数。
-
提取最后两位数字。如果你有更细粒度的需求,可以提取更多数字。
-
使用这些数字将 ID 分配给预定义实验桶中的一个。
客户映射到桶的概览
感谢你的关注,希望你觉得这篇文章有用。文中的所有图片均为我个人所有,欢迎使用!
非常感谢Deniss Zenkovs向我介绍这个想法并建议了有趣的扩展。
2024 年巴黎奥运会的明星运动员
如何使用维基百科数据可视化顶级运动员和奥林匹克运动的受欢迎程度
·发表于 Towards Data Science ·8 分钟阅读·2024 年 8 月 12 日
--
我们刚刚见证了为期三周的精彩体育赛事,2024 年奥运会在巴黎展开,数百万观众观看了直播并为他们喜爱的运动员加油。同时,我们看到一些家喻户晓的名字再次大放异彩,而下一代明星运动员也逐渐崭露头角。
作为一个好奇的数据科学家,我开始思考这些集体观点是如何发展的——哪些运动最受欢迎,哪些运动员正在崛起?为了将数据落实,我决定从维基百科进行广泛的数据收集,内容包括维基百科的个人资料和浏览量信息,然后比较不同运动项目的受欢迎程度以及顶级运动员的情况。以下是我的结果以及重现这些结果所需的所有 Python 代码。
所有图片均由作者创作。
维基百科上的奥林匹克运动
首先,让我们访问 2024 年夏季奥运会的维基百科页面,并使用 requests 库将其下载。然后,我使用 BeautifulSoup 包从 HTML 中提取信息——即所有夏季项目的列表及其维基页面链接。为了提取这些运动项目,我还会按照我的手动方法进行操作……
从正确的起点启动机器学习产品项目
学到的三大教训:问题、规模和数据
·发布于 Towards Data Science ·9 分钟阅读·2024 年 5 月 2 日
--
这篇博客文章是我去年在 GOTO 阿姆斯特丹的会议演讲部分内容的更新版本。演讲也可以在 这里观看。
作为一名机器学习产品经理,我对机器学习与产品管理的交集深感兴趣,尤其是在创造能够为产品、公司和用户带来价值和积极影响的解决方案时。然而,能够提供这种价值和积极影响并不是一项简单的工作。造成这种复杂性的一个主要原因是,在为数字产品开发的机器学习项目中,存在两个不确定性的来源相互交织。
从产品管理的角度来看,这一领域本质上是充满不确定性的。很难预测一个解决方案对产品的影响、用户的反应,以及它是否能够改善产品和商业指标……必须应对这种不确定性,这也是产品经理与其他角色(如项目经理或产品负责人)潜在不同之处。产品战略、产品发现、机会的规模估算、优先级排序、敏捷方法和快速实验等策略是克服这种不确定性的手段。
机器学习领域也与不确定性有着密切联系。我总是喜欢说“通过预测模型,目标是预测那些你不知道是可以预测的事情”。这意味着项目很难进行范围定义和管理,无法事先承诺质量交付物(良好的模型表现),并且许多举措永远停留在离线的概念验证阶段。清晰定义待解决的问题、初步数据分析和探索、从小做起、并紧密贴近产品和业务,这些都是有助于应对机器学习项目中不确定性的措施。
从一开始就缓解这一不确定性风险,是开发能够为产品、公司和用户带来价值的举措的关键。在这篇博客中,我将深入探讨我在启动机器学习产品项目时,如何从一开始管理不确定性的三大经验教训。这些经验主要来源于我作为数据科学家的第一手经验,以及如今作为机器学习产品经理的实践经验,有助于提高机器学习解决方案最终能够投入生产并产生积极影响的可能性。准备好一起探索吧:
-
从问题开始,并从一开始就定义预测结果的使用方式。
-
从小做起,如果可以的话,就保持小规模。
-
数据、数据、还是数据:质量、数量和历史。
从问题开始(并定义如何使用预测结果)
从正确的问题开始,Steve Johnson @ Pexels
我必须承认,我是通过艰苦的实践学到这一点的。我曾参与过一些项目,一旦模型开发完成,预测性能被认为“足够好”之后,模型的预测实际上对于任何特定的使用案例并不可用,或者不能有效地帮助解决任何问题。
造成这种情况的原因有很多,但我发现最常见的原因是:
-
以解决方案为驱动的举措:即使在生成式人工智能(GenAI)出现之前,机器学习和预测模型已经是“很酷”的解决方案,正因如此,一些举措是从机器学习解决方案开始的:“让我们试着预测流失”(指的是放弃某公司的用户或客户)、“让我们试着预测用户群体”… 当前生成式人工智能的炒作加剧了这一趋势,迫使公司将生成式人工智能解决方案“随便”地整合到各个适合的地方。
-
缺乏端到端的解决方案设计:在极少数情况下,预测模型是一个独立的解决方案。然而,通常情况下,模型及其预测会集成到更大的系统中,以解决特定的使用案例或启用新的功能。如果从一开始没有定义好这个端到端的解决方案,就可能导致模型在实施后被发现无用。
要让机器学习计划顺利起步,关键是从正确的问题开始。这是产品管理中的基础,产品领导者如Marty Cagan和Melissa Perri也反复强调这一点。它包括产品发现(通过用户访谈、市场调研、数据分析等),以及机会的量化和优先级排序(通过考虑定量和定性数据)。
一旦机会被识别,第二步是探索潜在的解决方案,其中应包括机器学习和生成式人工智能技术,如果它们能帮助解决问题的话。
如果决定尝试一种包含预测模型的解决方案,第三步将是进行解决方案或系统的端到端定义和设计。这样,我们可以确保系统如何使用预测结果的需求会影响预测部分的设计和实施(预测内容、使用的数据、实时与批量、技术可行性检查等)。
然而,我想补充的是,在这个话题上可能有一个显著的例外。如果生成式人工智能技术最终真的彻底改变了你的行业或我们所知的世界,那么从生成式人工智能解决方案开始,而不是从问题开始,是有意义的。关于这一点有很多讨论,但我认为现在还不清楚是否会发生这种情况。直到目前为止,我们已经在非常特定的行业中(如客户支持、营销、设计等)看到了这种革命,并且与人们在执行某些任务时的效率(如编程、写作、创作等)相关。然而,对于大多数公司来说,除非它被视为研发工作,否则交付短期/中期价值仍然应该意味着聚焦于问题,并将生成式人工智能视为解决这些问题的潜在解决方案之一。
从小做起(如果可能,保持小规模)
艰难的经历也带来了这些学习。这些经历的共同点是,一个大型的机器学习项目以瀑布式方式定义。这种项目通常预计持续 6 个月,并按照机器学习生命周期逐步推进。
遵循机器学习生命周期阶段的瀑布式项目规划,图像来自作者
这有什么可能出错呢,对吧?让我提醒你我之前的话 “通过预测模型,目标是预测那些你不知道能预测的事情”! 在这种情况下,可能会发生这样的情况:你在项目的第 5 个月时进行模型评估,意识到模型无法以足够好的质量预测任何它需要预测的内容。或者更糟糕的是,你在第 6 个月时,拥有一个已经部署到生产环境中的超级模型,却意识到它没有带来任何价值。
这种风险与与产品相关的不确定性结合在一起,迫使我们尽可能避免大型瀑布式项目。这不仅仅是 ML 项目所特有的问题,也并非新鲜事物,因此我们可以从传统软件开发、敏捷开发、精益方法及其他方法论和思维方式中学到很多。通过从小规模开始,快速验证假设并持续进行实验和扩展,我们可以有效地规避这一风险,适应洞察并提高成本效益。
尽管这些原则在传统软件和产品开发中已经得到了充分确立,但将其应用于 ML 项目则要复杂一些,因为定义一个 ML 模型和部署的“简单”并不容易。然而,还是有一些方法可以帮助在 ML 项目中从小处着手。
基于规则的方法,通过决策树简化预测模型。通过这种方式,“预测”可以轻松地作为“if-else 语句”在生产中实现,作为功能或系统的一部分,而不需要部署模型。
概念验证(POC),作为一种离线验证 ML 解决方案预测可行性的方法,并在生产中给出预测步骤的潜力(或不具备潜力)。
最小可行产品(MVP),首先关注核心功能、特性或用户群体,只有在价值被验证后,才扩展解决方案。对于一个 ML 模型,这可能意味着仅使用最直接的优先输入特征,或者仅为一部分数据点进行预测。
购买而非自建,利用现有的 ML 解决方案或平台,以帮助减少开发时间和初期成本。只有在证明有价值且成本过高时,才可能是决定在内部开发 ML 解决方案的合适时机。
将 GenAI 作为 MVP 使用,对于一些用例(尤其是涉及文本或图像的用例),可以将 GenAI API 作为解决系统预测步骤的首选方法。像文本分类、情感分析或图像检测等任务,GenAI 模型能够提供令人印象深刻的结果。当价值得到验证且成本过高时,团队可以决定开发一个特定的“传统” ML 模型。
请注意,尽管使用 GenAI 模型进行图像或文本分类是可能的并且快速,但这意味着在解决本可以用更简单、可控的模型预测的问题时,使用了一个过于庞大且复杂的模型(昂贵、缺乏控制、幻觉等)。一个有趣的类比是用卡车送披萨:虽然可行,但为什么不直接用自行车呢?
数据、数据和数据(质量、量、历史)
图片来源:Tima Miroshnichenko,来自 Pexels
数据是数据科学家和机器学习团队在启动机器学习项目时遇到的反复出现的问题。你是否曾被重复的数据、错误、缺失的批次、奇怪的值……所惊讶?这与你在在线课程中看到的玩具数据集有多大的不同!
也有可能是你需要的数据根本不存在:某些特定事件的跟踪从未被实现,收集和适当的 ETL 最近才实施……我曾经历过这样的情况:为了获得足够的历史数据和数据量,不得不等待几个月才能启动一个项目。
所有这一切都与“垃圾进,垃圾出”这句格言相关:机器学习模型的效果取决于它们所训练的数据。很多时候,通过改善数据而非模型来提高解决方案的潜力更大(数据中心化 AI)。数据需要在数量、历史(多年生成的数据比在短短一周内生成的数据更有价值)和质量上都足够。为了实现这一点,成熟的数据治理、收集、清洗和预处理至关重要。
从伦理 AI的角度来看,数据也是偏见和歧视的主要来源,因此认识到这一点并采取行动来减轻这些风险至关重要。考虑数据治理原则、隐私和合规性(例如欧盟的GDPR)也是确保负责任使用数据的关键(尤其是在处理个人数据时)。
使用GenAI 模型时,这一情况发生了转变:大量数据已经被用于训练这些模型。在使用这些类型的模型时,我们可能不需要大量和高质量的数据进行训练,但可能需要它们来进行微调(见优质数据 = 优质 GenAI),或者构建提示(培养上下文、少量示例学习、检索增强生成……—我在之前的文章中解释了所有这些概念!)。
需要注意的是,通过使用这些模型,我们正在失去对用于训练它们的数据的控制,我们可能会受到使用的数据的质量或类型的影响:有很多已知的偏见和歧视的例子,这些例子会对我们的解决方案产生负面影响。一个很好的例子是彭博社的文章“ChatGPT 如何成为招聘人员的梦想工具——测试显示存在种族偏见”。LLM 排行榜测试偏见,或专门训练以避免这些偏见的 LLM在这方面可能会有所帮助。
关于 ChatGPT 的性别偏见示例(2024 年 5 月 1 日的提示)
总结
我们在这篇博客文章的开头讨论了使机器学习产品项目特别棘手的因素:这涉及到在开发数字产品解决方案过程中所面临的不确定性,以及通过使用机器学习模型来预测事物时所遇到的不确定性。
知道有可操作的步骤和策略可以用来缓解这些风险,令人感到宽慰。然而,也许最好的方法是,从一开始就把这些项目启动在正确的轨道上!为此,确实有助于从正确的问题和端到端的解决方案设计入手,减少初期的范围,并优先考虑数据质量、数量和历史准确性。
我希望这篇文章对你有所帮助,并能帮助你挑战未来与机器学习产品相关的新项目工作方式!
视频生成的现状
视频生成领域的最新发展概述:从大语言模型(LLMs)到多重扩散(MultiDiffusion)。演变与关键问题。
·发布于 Towards Data Science ·16 分钟阅读·2024 年 2 月 16 日
--
在 Runway Gen2 中生成的动画 | 录像机的缩小镜头
虽然 2023 年是大语言模型(LLMs)和图像生成技术爆发的一年,但视频生成技术却相对较少受到关注。在研究这一话题时,我发现很难跟上最新的发展和整体架构设计,因为它们代表了各种各样的模型。
在这篇文章中,我旨在分享近年来视频生成技术的发展,模型架构的演变,以及我们现在面临的关键问题。
在写这篇文章时,OpenAI 发布了 Sora——一款功能惊人的视频生成模型。尽管其架构尚未公开,但我希望你能从中获得一些洞察,了解它可能的构建方式。
让我们深入探讨时间线
打分偏差的统计分析
2024 年阿根廷探戈世界锦标赛
·发表于Towards Data Science ·阅读时间:19 分钟·2024 年 10 月 1 日
--
2024 年 Tango de Pista 世界舞蹈大赛冠军:法蒂玛·卡拉科奇与布雷诺·马尔凯斯。图像来源:TangoBA
简而言之
-
评审小组之间偏差的比例检验具有统计显著性。
-
数据可视化显示了高度偏斜的分布
-
对评审员和评审小组之间相对均值偏差的测试表明,评审小组 1 存在负偏差(给出较低的分数),而评审小组 2 存在正偏差(给出较高的分数)。
-
对各评审小组之间的均值偏差测试未能提供统计显著的差异证据,但测试的统计功效较低。
图像来源:TangoBA
Tango de Mundial是每年在阿根廷布宜诺斯艾利斯举办的阿根廷探戈比赛。来自世界各地的舞者汇聚布宜诺斯艾利斯,争夺世界冠军的头衔。
在 2024 年,约有 500 对舞蹈搭档(1000 名舞者)参加了预选赛。仅有一小部分舞者晋级半决赛,最终约 40 对舞者进入决赛。仅仅晋级 2024 年的决赛,你就已经超过了全球 95%的竞争者。
许多决赛选手利用这个平台推动自己的职业生涯,而世界冠军的头衔则将你的面孔永载探戈历史,几乎可以保证你作为职业舞者工作,直到你不再需要为此奋斗。因此,对于许多舞者来说,他们的命运掌握在评审们对其舞蹈的评价之中。
在本文中,我们将对两个由 10 名评委组成的评审小组的评分偏差进行多项统计分析。每位评委都有自己对“什么是探戈?”这一问题的回答。每位评委对评判标准的质量有不同的看法:技巧、音乐性、拥抱、舞蹈词汇、舞台表现(即,你看起来像不像舞者)等。正如你已经察觉到的,这些评估高度主观,因此毫不奇怪,评委之间会产生很大的偏差。
注意: 除非明确说明,否则所有数据可视化(即图表、图形、数据框截图)均为作者原创作品。
代码
你可以在我的GitHub 个人主页找到所有用于本次分析的代码。
[## my_portfolio/Argentine_Tango_Mundial at main · Alexander-Barriga/my_portfolio
在这里,你可以找到我参与的一些个人数据科学项目。 - my_portfolio/Argentine_Tango_Mundial at main ·…
数据限制
在我们深入分析之前,让我们先讨论一下数据的限制。你可以直接访问这份 PDF 文件查看初赛的得分。
[## Ranking-Clasificatoria-Tango-de-Pista-Mundial-de-tango-2024.pdf
编辑描述
来自初赛轮次的舞者得分第 1 页。每一行代表一个舞蹈双人组。注意,每对舞者的评分来自 20 位评委中的 10 位。更多信息请见数据分析部分。数据截图由作者提供。
-
评委并不代表整个世界。 虽然舞蹈选手在某种程度上能代表全球探戈舞蹈人群,但评委并不能——他们全都是阿根廷人。这使得一些人质疑比赛的合法性。至少,这样的情况使得“Munidal de Tango”这一名称被认为是错误的。
-
将舞者评分精确到小数点后 100 位是荒谬的。 一些评委会将舞者评分精确到小数点后 100 位(即 7.75 与 7.7)。这引发了一个问题:“是什么样的舞蹈质量会导致 0.05 的分差?”至少,这凸显了评分的高度主观性。显然,这并不是一个使用高度可靠和精密测量设备的实验室物理实验。
-
腐败与政治。 在舞蹈社区中并不是什么秘密,如果你参加了评审团成员的课程,你很可能会从他们那里获得积极的偏见(无论是有意的还是无意的),因为你代表了他们的思想体系,这使得他们在你的成功中有既得利益,并且代表了一种利益冲突。
-
仅提供舞者分数。 不幸的是,除了舞者的名字和分数外,节日组织者并没有发布其他数据,如经验年数、年龄或原籍国,这大大限制了更全面的分析。
-
艺术是高度主观的。 在探戈中有许多不同的思想流派,就像舞者有不同的意见一样。每个舞者都有自己的探戈哲学,定义了什么是好的技巧,或者什么样的拥抱感觉是美好的。
-
专业知识。 我们讨论的不是电影或美食评论。舞者需要经过多年的高度专注的训练,才能培养出自己对阿根廷探戈的品味和有见地的意见。对于外行人来说,难以将数据映射到舞者在舞台上的表现。
尽管数据存在这些问题,Tango Munidal 的数据仍然是我们可以获得的最大、最具代表性的全球阿根廷探戈舞者数据集。
相信我——我的资历
除了是数据科学家外,我还是一名竞技阿根廷探戈舞者。虽然我没有参加 2024 年 Tango Munidal 比赛,但我已经多年来在这项舞蹈上进行了刻苦训练(除了其他舞蹈和武术)。我是一个舞者,一个武术家,一个表演者,也是探戈的守护者。
虽然我的观点仅代表了阿根廷探戈中的一个主观声音,但它是一个真实且有见地的声音。
舞蹈评分中的偏差统计分析
接下来我们将进行几项统计测试,以评估评分偏差是否存在,以及在哪些地方存在偏差。分析的概要如下:
-
各评审团之间的比例偏差测试
-
数据可视化与比较评审在不同均值偏差中的表现
-
测试评审间的相对平均偏差
-
测试评审团之间的均值偏差
1. 偏差的比例测试
再次看看数据表第 1 页中表现最好的舞蹈情侣:
数据表的第 1 页。由作者提供的数据截图。
从左到右阅读表示评审名字的列名,在Jimena Hoffner和Noelia Barsel之间,你会看到:
-
第 1 至第 5 位以及第 11 至第 15 位的评审属于我们将称之为评审团 1的组别。
-
第 6 至第 10 位以及第 16 至第 20 位的评审属于我们将称之为评审团 2的组别。
注意到了什么吗? 注意到由第二评审小组评审的舞者比例明显更大,而由第一评审小组评审的舞者比例较小。如果你浏览PDF 的数据表,你会看到,这种比例差异在所有晋级到半决赛的选手中都得到了验证。
备注: 绿色阴影部分的舞者晋级到了半决赛,而没有被绿色阴影标出的舞者则没有晋级。
这就引出了一个问题,这种比例差异是真实的吗,还是由于随机抽样,即舞者被随机分配到一个小组而不是另一个小组?嗯,我们有一个统计检验可以用来回答这个问题。
双尾检验:检验两个总体比例是否相等
我们将使用双尾 z 检验来测试两个比例是否在任一方向上存在显著差异。我们关心的是一个比例是否显著不同于另一个比例,无论它是更大还是更小。
统计检验假设
-
随机抽样:样本必须从各自的总体中独立且随机地抽取。
-
大样本量:样本量必须足够大,以便样本比例差异的抽样分布可以近似为正态分布。这一近似来自于中心极限定理。
-
预期的成功与失败次数:为了确保正态近似成立,每个组中预期的成功和失败次数应该至少为 5。
我们的数据集满足所有这些假设。
进行检验
- 定义我们的假设
原假设: 每个分布的比例相同。
备择假设: 每个分布的比例不相同。
2. 选择统计显著性水平
alpha 的默认值为 0.05(5%)。我们没有理由放宽这个值(例如 10%)或使其更加严格(例如 1%)。所以我们将使用默认值。Alpha 表示我们因随机抽样而错误拒绝原假设,支持备择假设的容忍度(即第一类错误)。
接下来,我们将使用下面提供的 Python 代码来进行检验。
def plot_two_tailed_test(z_value):
# Generate a range of x values
x = np.linspace(-4, 4, 1000)
# Get the standard normal distribution values for these x values
y = stats.norm.pdf(x)
# Create the plot
plt.figure(figsize=(10, 6))
plt.plot(x, y, label='Standard Normal Distribution', color='black')
# Shade the areas in both tails with red
plt.fill_between(x, y, where=(x >= z_value), color='red', alpha=0.5, label='Right Tail Area')
plt.fill_between(x, y, where=(x <= -z_value), color='red', alpha=0.5, label='Left Tail Area')
# Define critical values for alpha = 0.05
alpha = 0.05
critical_value = stats.norm.ppf(1 - alpha / 2)
# Add vertical dashed blue lines for critical values
plt.axvline(critical_value, color='blue', linestyle='dashed', linewidth=1, label=f'Critical Value: {critical_value:.2f}')
plt.axvline(-critical_value, color='blue', linestyle='dashed', linewidth=1, label=f'Critical Value: {-critical_value:.2f}')
# Mark the z-value
plt.axvline(z_value, color='red', linestyle='dashed', linewidth=1, label=f'Z-Value: {z_value:.2f}')
# Add labels and title
plt.title('Two-Tailed Z-Test Visualization')
plt.xlabel('Z-Score')
plt.ylabel('Probability Density')
plt.legend()
plt.grid(True)
# Show plot
plt.savefig(f'../images/p-value_location_in_z_dist_z_test_proportionality.png')
plt.show()
def two_proportion_z_test(successes1, total1, successes2, total2):
"""
Perform a two-proportion z-test to check if two population proportions are significantly different.
Parameters:
- successes1: Number of successes in the first sample
- total1: Total number of observations in the first sample
- successes2: Number of successes in the second sample
- total2: Total number of observations in the second sample
Returns:
- z_value: The z-statistic
- p_value: The p-value of the test
"""
# Calculate sample proportions
p1 = successes1 / total1
p2 = successes2 / total2
# Combined proportion
p_combined = (successes1 + successes2) / (total1 + total2)
# Standard error
se = np.sqrt(p_combined * (1 - p_combined) * (1/total1 + 1/total2))
# Z-value
z_value = (p1 - p2) / se
# P-value for two-tailed test
p_value = 2 * (1 - stats.norm.cdf(np.abs(z_value)))
return z_value, p_value
min_score_for_semi_finals = 7.040
is_semi_finalist = df.PROMEDIO >= min_score_for_semi_finals
# Number of couples scored by panel 1 advancing to semi-finals
successes_1 = df[is_semi_finalist][panel_1].dropna(axis=0).shape[0]
# Number of couples scored by panel 2 advancing to semi-finals
successes_2 = df[is_semi_finalist][panel_2].dropna(axis=0).shape[0]
# Total number of couples that where scored by panel 1
n1 = df[panel_1].dropna(axis=0).shape[0]
# Total sample of couples that where scored by panel 2
n2 = df[panel_2].dropna(axis=0).shape[0]
# Perform the test
z_value, p_value = two_proportion_z_test(successes_1, n1, successes_2, n2)
# Print the results
print(f"Z-Value: {z_value:.4f}")
print(f"P-Value: {p_value:.4f}")
# Check significance at alpha = 0.05
alpha = 0.05
if p_value < alpha:
print("The difference between the two proportions is statistically significant.")
else:
print("The difference between the two proportions is not statistically significant.")
# Generate the plot
# P-Value: 0.0000
plot_two_tailed_test(z_value)
Z 值是我们计算得到的统计值。请注意,它远远超出了标准正态分布的范围。
图表显示,计算得到的 Z 值远远超出了我们在原假设为真的情况下期望看到的 z 值范围。因此,p 值为 0.0,表明我们必须拒绝原假设,支持备择假设。
这意味着比例差异是真实的,而不是由于随机抽样造成的。
-
17%的舞蹈组合在第一评审小组的评审下晋级到半决赛
-
42%的舞蹈组合在第二评审小组的评审下晋级到半决赛
我们的第一个偏差统计测试提供了证据,表明在由第二组评委评判的舞者的评分中存在正向偏差,几乎是 2 倍的提升。
接下来,我们将深入分析每位评委的评分分布,并看看他们各自的偏差如何影响整个小组的评分偏差。
2. 数据可视化
本节中,我们将分析每位评委的个人评分分布和偏差。以下 20 个直方图展示了每位评委给舞者打的分数。请记住,每位舞者都由第 1 组或第 2 组的所有 10 位评委评分。评委的直方图是随机排列的,即第一列并不代表第 1 组的评委。
注意: 评委评分的范围是 1 到 10 分。
来自所有 20 位评委的评分分布。标题包含评委的姓名。
注意,有些评委的评分比其他评委严格得多。这引出了以下问题:
-
评委之间的偏差分布是什么样的?换句话说,哪些评委评分更严厉,哪些评分更宽松?
-
评委的评分偏差是否会被他们组内其他评委抵消?如果没有,评委之间的平均分数是否存在统计差异?
我们将在第三部分回答第 1 个问题。
我们将在第四部分回答第 2 个问题。
正如我们在上面的直方图中看到的,评委 Martin Ojeda 是最严厉的舞蹈评委。让我们看看他的 QQ 图。
Martin 给出的评分分布。
左下偏差(x 轴小于-2):在下分位区(最左侧),数据点实际上偏离了红线以上。这表明对于最弱的表演,评分比预期的高。Martin 可能是出于同情,稍微提高了对舞蹈组合的评分。一个潜在的原因可能是,Martín 希望避免对表现最差的选手打出极低的分数,从而给出稍微高一些的分数。
- Martín 略微高估了较弱的竞争者,这可能表明他对那些本来应该得低分的表演有轻微的正向偏差。
评分差异的稀释:如果较弱的表演被高估,这可能会压缩评分范围,使较弱和中等水平的竞争者之间的差距变小。这可能会使强、适中和弱的表演之间的差异不那么明显。
- 例如,如果一场弱的表演得到一个较高的分数(比如 5.5 或 6.0),而不是 4.0 或 4.5,而中等水平的表演得分为 6.5,那么弱者和中等竞争者之间的差距就被人为缩小了。这破坏了竞争的公平性,因为这些分数不再真实反映表演质量。
低分和高分之间的平衡:尽管马丁高估较弱的表演者,但注意到在中间分数段(6.0–7.0),得分紧密跟随正态分布,表现出更中立的行为。然而,这种情况被他在高分段的慷慨评分(对顶级表演者的正偏见)所抵消,表明马丁倾向于“拉高”表现光谱的两端。
- 总的来说,这种高估最弱和最强竞争者的组合会压缩中间的得分。中等水平的竞争者可能会因此受到最大的不利影响,因为他们被夹在被高估的较弱表现和被慷慨评分的较强表现之间。
在下一节中,我们将识别出马丁认为最佳舞蹈搭档的极端得分,并在与评审小组中其他评委的得分对比时,为这对舞蹈搭档的得分提供更多背景。
在 Jupyter Notebook 中还有 19 个其他的 QQ 图,我们在本文中不会一一介绍,因为这会让文章变得冗长不堪。不过,您可以自行查看。
3. 测试评委间相对均值偏差
在本节中,我们将回答上一节提出的第一个问题。本节将分析各个评委的评分偏差。下一节将关注评审小组之间的评分偏差。
评委之间的偏差分布是怎样的?换句话说,哪些评委评分更严苛,哪些评委评分更宽松?
我们将进行迭代 T 检验,以检查某位评委的平均分是否在统计学上与其他 19 位评委的平均分的均值不同;也就是说,计算其他 19 位评委的平均分的均值。
# Calculate mean and standard deviation of the distribution of mean scores
distribution_mean = np.mean(judge_means)
distribution_std = np.std(judge_means, ddof=1)
# Function to perform T-test
def t_test(score, mean, std_dev, n):
"""Perform a T-test to check if a score is significantly different from the mean."""
t_value = (score - mean) / (std_dev / np.sqrt(n))
# Degrees of freedom for the T-test
df = n - 1
# Two-tailed test
p_value = 2 * (1 - stats.t.cdf(np.abs(t_value), df))
return t_value, p_value
# Number of samples in the distribution
n = len(judge_means)
# Dictionary to store the test results
results = {}
# Iterate through each judge's mean score and perform T-test
for judge, score in zip(judge_features, judge_means):
t_value, p_value = t_test(score, distribution_mean, distribution_std, n)
# Store results in the dictionary
results[judge] = {
'mean_score': score,
'T-Value': t_value,
'P-Value': p_value,
'Significant': p_value < 0.05
}
# Convert results to DataFrame and process
df_judge_means_test = pd.DataFrame(results).T
df_judge_means_test.mean_score = df_judge_means_test.mean_score.astype(float)
df_judge_means_test.sort_values('mean_score')
按评委的平均分值排序的 T 检验结果。
这里有 20 位评委:他们的平均分、t 统计量、p 值,以及每位评委的平均分与其他 19 位评委的平均分分布的差异是否在统计学上显著。
我们有三组评委:那些评分非常严格(统计学上低于平均水平),那些通常给出平均分(统计学上在平均范围内),以及那些评分较为宽松(统计学上高于平均水平)。
让我们关注三位具有代表性的评委:马丁·奥赫达、法昆多·多拉·克鲁兹和诺埃利亚·巴尔西。
这三位评委代表了所有 20 位评委中的最严格、典型和宽松的评分偏差倾向。
请注意,马丁·奥赫达的得分分布和均值(蓝线)相比于所有评审的平均得分均值(红线)偏向较低的值。但我们也看到他的分布大致呈正态分布,除了少数几个离群值。我们稍后会回到得分为 7.5 的那对舞伴。几乎任何由马丁评分的舞蹈组合,都会看到他们的总平均分受到影响。
法昆多·德·拉·克鲁兹的得分分布大致呈正态分布,且方差较大。他是相对于其他同行而言偏差最小的评审。几乎任何由法昆多评分的舞蹈组合,都可以期待一个接近所有评审得分的结果,因此他们不必担心负面偏差,但也不太可能获得高分,从而提高他们的总体平均分。
诺埃利亚·巴里斯代表的是那种相比其他评审,倾向于给更多舞者一个有利评分的评审。所有舞蹈组合都应该希望诺埃利亚被分配到他们的评审小组。
现在让我们回到马丁的离群值舞伴。在马丁看来,卢卡斯·卡塔赫纳 和 卢西拉·迪亚兹·科洛德雷罗 的舞蹈表现就像离群值一样。
卢卡斯·卡塔赫纳 和 卢西拉·迪亚兹·科洛德雷罗。PROMEDIO 意味着平均值。
-
马丁·奥赫达给了这对舞蹈组合第四高的得分(7.600),而他的得分高于 7.326 的平均得分(PROMEDIO)
-
虽然马丁·奥赫达的得分在与他自己分布的比较中是一个离群值,但它并不像这对舞蹈组合收到的其他得分那样高。这意味着两件事:
-
马丁·奥赫达是一个整体保守的评分者(正如我们在上一节所见)
-
当与其他评审的得分相比时,马丁·奥赫达的离群得分实际上是有意义的,这表明这对舞蹈组合确实表现突出
这里他们演绎的是一种名为米隆加的探戈子类别,这种舞蹈通常与其他类别的音乐(如探戈和华尔兹)有很大的不同,通常不包括旋转动作,而是包括小而快速的步伐,强调音乐性。我可以告诉你,他们在这段视频中的表现非常出色,我想大多数舞者都会同意这个评价。
享受表演。
4. 测试评审小组之间均值偏差
在本节中,我们通过回答以下问题来测试评审组 1 和评审组 2 之间的偏差:
评审的评分偏差是否会被他们小组内其他评审的评分所抵消?如果没有,评审均值之间是否存在统计学差异?
我们将通过两种方式测试评审偏差
-
排名基础的评审偏差
-
两尾 T 检验,比较评审组 1 和评审组 2 的得分分布
排名基础的评审偏差
在这里,我们将对评审的均分进行排名和标准化,并计算每个评审小组的偏差。这是我们衡量评审小组之间潜在偏差的一种方法。
panel_1 = judge_features[:5] + judge_features[10:15]
panel_2 = judge_features[5:10] + judge_features[15:]
df_judge_means = df_judge_means_test.sort_values('mean_score').mean_score
# Calculate ranks
ranks = df_judge_means.rank()
# Calculate mean and std of ranks
means_ranks = ranks.mean()
stds_ranks = ranks.std()
# Standardize ranks
df_judge_ranks = (ranks - means_ranks) / stds_ranks
df_judge_ranks
# these are the same judges sorted in the same way as before based on their mean scores
# except here we have converted mean values into rankings and standardized the rankings
# Now we want to see how these 20 judges are distributed between the two panels
# do the biases for each judge get canceled out by their peers on the same panel?
所有 20 位评审的排序排名
我们将每个评委的平均分值替换为相对于同行的排名。马丁仍然是最严苛、偏见最负面的评委。诺埃利亚仍然是偏见最正面的评委。
面板 1
面板 2
请注意,面板 1 中的大多数评委偏向负面,只有 4 位评委偏向正面。面板 2 中的大多数评委偏向正面,只有 2 位评委偏向负面,法昆多则大致中立。
如果面板内部的偏差相互抵消,那么个别评委的偏见实际上不再重要;任何舞蹈搭档都会被公平评分。但是如果面板内部的偏差没有抵消,那么可能会存在不公平的优势。
面板 1 的平均排名值:-0.39478
面板 2 的平均排名值:0.39478
我们发现,平均面板排名表明面板内部的偏差并未相互抵消,并提供了证据表明舞蹈搭档在面板 2 评审下有优势。
面板 1 和面板 2 分布之间的双尾 T 检验。
接下来,我们继续对面板偏差进行进一步的检验。下方图表展示了两个分布。蓝色表示分配给面板 1 的评委给出的平均分布,橙色表示分配给面板 2 的评委给出的平均分布。
在图表中,你会看到每个面板的平均分。面板 1 的平均分为 6.62,面板 2 的平均分为 6.86。
尽管 0.24 的面板平均差异看起来较小,但请注意,晋级半决赛的差异仅由 0.013 的差异决定。
两个面板评委的平均分布。
在进行两样本 T 检验后,我们发现面板平均分之间的差异在统计上并不显著。该检验未能提供额外证据表明面板 1 和面板 2 之间的偏差存在统计差异。
该检验的 P 值为 0.0808,与我们默认的 0.05 的显著性水平相差不大。
大数法则
我们知道,来自大数法则的结论表明,在小样本分布中(两个面板各有 10 个数据点),我们通常会发现方差大于其对应总体分布的方差。然而,随着样本数的增加,样本分布的参数将趋近于总体分布的参数(即均值和方差)。这可能是 T 检验未能提供面板偏差不同的证据的原因,即由于方差较大。
统计功效
另一种理解我们所看到结果的方式是通过统计效能。 统计效能指的是测试正确拒绝虚假零假设的概率。统计效能受到几个因素的影响。
-
样本大小
-
效应量(你想要检测的真实差异或关系)
-
显著性水平(例如,α=0.05)
-
数据的变异性(标准差)
增加我们测试统计效能最可靠的方法是收集更多的数据点,然而在这里这是不可行的。
结论
本文我们将探讨 2024 年在布宜诺斯艾利斯举行的世界探戈锦标赛初赛数据。我们通过四种方式分析了评委和评审团的评分偏差:
-
测试评审团之间的比例偏差
-
数据可视化
-
测试评委之间的相对均值偏差
-
测试评审团之间的均值偏差
偏差证据
-
我们发现,被评审团 2 的评委评分的舞者晋级半决赛的比例显著更高。
-
个别评委的评分分布差异很大:有些偏高,有些偏低。我们看到,马丁·奥赫达对表现最差和最好的一对舞伴有明显的正偏向,他通过“拉高”他们的得分并压缩中等水平的舞者得分。总体来看,他的评分分布远低于其他所有评委。有些评委给出了更典型的分数,而有些评委则给出了非常宽松的分数(与同行相比)。
-
评审团之间的相对均值偏差存在明显差异。评审团 1 的大多数评委被认为在评分上存在负偏向,而评审团 2 的大多数评委则存在正偏向。因此,评审团 1 在评分舞者时表现出总体的负偏向,而评审团 2 则表现出总体的正偏向。
没有偏差证据
- 发现评审团 1 和评审团 2 之间的均值差异在统计学上没有显著性。由于分布的样本量较小,导致统计效能较低。因此,这个测试不如其他测试可靠。
我的结论是,个别评委和评审团层面上都有足够的偏差证据。分配到评审团 2 的舞者确实拥有一定的竞争优势。因此,对于寻求在比赛中获得额外优势的潜在竞争舞者,我们有一些建议。
如何赢得舞蹈比赛
存在非权谋主义和权谋主义两种方式可以增加你获胜的机会。本文的结果和经验背景知识为权谋主义方法提供了更多的信服依据。
非权谋主义
- 训练。永远没有什么能替代你投入 10000 小时的练习以获得精通的过程。
权谋主义
-
上课并与那些你知道会出现在你评审小组中的教练建立关系。你的成功意味着该评审员舞蹈风格的验证和他们业务的推广。如果你的资源有限,可以专注于那些评分严格的评审,以帮助提高你的平均分。
-
如果有多个评审小组,找到一种方法,确保你被分配到那个对你更有利的评审小组,从而提高你晋级下一轮的机会。
最终想法
我个人认为,尽管在舞蹈比赛中玩弄权力游戏有一定的实用性,但这其实很愚蠢。没有什么能替代天赋,尤其是那种原始且无可辩驳的天赋。这篇分析最终是一个有趣的激情项目,也是一个应用一些统计概念的机会,而这个话题我非常珍惜。
关于作者
亚历山大·巴里加是一位数据科学家和竞技阿根廷探戈舞者。他目前住在加利福尼亚州洛杉矶。如果你已经看到这里,考虑给出反馈,分享他的文章,或者直接与他联系提供工作机会——他正在积极寻找下一个数据科学职位!
图片来自作者。
使用 Python 进行统计分析:癌症治疗数据的洞见
步骤式探索统计方法、数据可视化和回归分析
·发布于Towards Data Science ·31 分钟阅读·2024 年 11 月 25 日
--
概览
本项目专注于使用 Python 进行统计分析,应用各种技术从虚构数据集中挖掘洞见,灵感来源于现实世界的癌症治疗实验。
尽管该数据集本身是模拟的,但该项目汲取了如麦吉尔大学操作规程等研究标准的灵感,强调数据准确性和伦理实验设计。
该数据集代表了一项涉及100 只小鼠的研究,这些小鼠患有鳞状细胞癌(SCC),这是一种皮肤癌,并接受了四种不同治疗方案。在45 天内,观察并记录了肿瘤的发展,为分析提供了丰富的数据集。
本项目特别引人注目,因为即使使用的是虚构数据,它也能反映现实世界中的应用和挑战。目标是展示一旦掌握了数据分析技巧,你就可以将它们应用到任何数据集或领域。
通过访问数据字典并清楚理解变量,你将拥有所有工具来探索数据集。我将一步一步地引导你…
统计收敛及其后果
一幅描绘约 1800 年代的桨轮蒸汽船的画作,船只正遭遇风暴,船员已放弃她。艺术家:约翰·亨德里克·路易斯·梅耶(Johan Hendrik Louis Meijer)。(公有领域图片)
这是一个关于随机变量收敛的故事,发生在尼姆罗德号这次非凡航行的背景下。
·发表于Towards Data Science ·30 分钟阅读·2024 年 5 月 15 日
--
1860 年 2 月 25 日星期六早晨,客轮尼姆罗德号(Nimrod)从英格兰利物浦的码头启航,目的地是爱尔兰的科克港——大约 300 英里的距离。尼姆罗德号之前已经多次往返这条航线,像她一样的数百艘船只穿越爱尔兰和英格兰之间的海域,将来自爱尔兰港口的家庭接走,并送往英格兰的港口,随后他们会继续移民到美国。
尼姆罗德号是一艘强劲的铁桨蒸汽船,配备蒸汽发动机和全套帆具——是她那个时代现代、强大且经过海上考验的船只。船长莱尔(Lyall)掌舵——他是一位经验丰富的海员,曾参与过克里米亚战争,并且是船员们敬爱的、深受喜爱的领导者。船上还有 45 名船员和价值数千英镑的货物。1860 年是一个异常寒冷和潮湿的年份,但在 2 月 25 日的那个周末,海面平静,天际没有暴风雨的迹象。
斯莫尔斯灯塔,拍摄于 2018 年夏季。原始结构在 1831 年遭受严重的风暴损坏后,于 1857 年至 1861 年间重新修建。1978 年在其上建造了一个直升机停机坪。(来源:Wikimedia 采用CC BY-SA 4.0协议)
统计方法 scDEED 检测可疑的 t-SNE 和 UMAP 嵌入并优化超参数
scDEED 为每个 2D 嵌入分配一个可靠性评分,以表示数据点在 2D 空间中其中程邻居的变化程度。
·发表于Towards Data Science ·阅读时间 6 分钟·2024 年 3 月 5 日
--
本文由 Christy Lee 撰写,她是 UCLA 统计学与数据科学博士生。Christy 是 scDEED 在《自然通讯》期刊发表文章的共同第一作者。
图片来源:Viktor Forgacs于Unsplash
t-SNE(t-分布随机邻域嵌入)和 UMAP(统一流形逼近与投影)是用于可视化高维数据的非线性降维技术,尤其在单细胞分析中用于可视化细胞群集。然而,重要的是要注意,t-SNE 和 UMAP 可能并不总是能够提供细胞群集之间相对距离的可信表示。
在我们的《自然通讯》论文1中,我们提供了一个框架,用于(1)识别从高维空间投影到二维(2D)空间中的数据扭曲,以及(2)优化二维降维方法中的超参数设置。
Xia, L., Lee, C. & Li, J.J. Statistical method scDEED for detecting dubious 2D single-cell embeddings and optimizing t-SNE and UMAP hyperparameters. Nat Commun 15, 1753 (2024). doi.org/10.1038/s41467-024-45891-y
考虑一个三维(3D)地球与一个二维(2D)地图。仅用二维就不可能准确地表示整个地球;距离可能不准确,一些国家的大小可能会被扭曲。通常,地图边缘的陆地,如南极洲,是变化最大的一部分。尽管存在这些扭曲,二维地图仍然适用于日常使用;学生或普通的大陆旅行者不会受到南极洲扭曲的影响,但一位勇敢的极地旅行者显然需要一张不同的地图。
同样,单细胞基因组学数据的表示通常需要从高维空间转换到二维空间,所谓的二维嵌入。与地球的转换类似,这可能会引入扭曲。二维嵌入后的空间可能无法准确表示预嵌入空间。更麻烦的是,流行的二维嵌入方法,如 t-SNE 和 UMAP,对超参数选择非常敏感。虽然有一些通用的指导原则可以根据数据集的大小来调整超参数,如困惑度(perplexity)和邻居数(n.neighbors),但这些指导原则并不能帮助解答一个根本性的问题——可视化的哪些部分是误导的?
类似于制图师选择忠实重建哪些陆地、扭曲哪些陆地,研究人员必须优先考虑在嵌入后哪些预嵌入空间的方面是最重要的需要保留的。二维可视化的常见用途包括细胞轨迹和簇的注释与分析。尽管细胞轨迹和簇通常是在高维空间中计算的,但它们的结果通常通过二维嵌入进行可视化,在这种嵌入中,基因表达相似的细胞预计彼此接近。因此,我们得出结论,保留的最重要方面是细胞之间相对位置的保持。
这些想法构成了 scDEED 的动机,它是一个单细胞可疑嵌入检测器(见图 1)。关键思想是,细胞的预嵌入和后嵌入邻居应该相似。值得注意的是,单细胞数据分析中的预嵌入空间通常是 20 到 50 维的,通常是主成分空间。对于每个细胞,我们计算一个可靠性得分,该得分反映了在二维嵌入空间和预嵌入空间中找到的邻居之间的视觉一致性。那些在嵌入过程中邻居发生显著变化的细胞被称为“可疑”细胞;这些细胞的相对位置是误导性的,并没有反映出基于预嵌入空间应有的位置。识别这些细胞提供了一种优化超参数的机制,通过选择那些能够产生最少可疑细胞嵌入的设置。
图 1. scDEED 的两个功能示意图。功能 I 通过计算可靠性评分来决定每个细胞的嵌入是否为可信或可疑,其中可靠性评分定义为该细胞在 2D 嵌入空间中与最近的 50%邻近细胞的距离与该细胞在预嵌入空间中与最近的 50%邻近细胞的距离之间的 Pearson 相关性。在功能 I 的支持下,功能 II 通过最小化可疑嵌入的数量,优化嵌入方法的超参数设置(例如 t-SNE 和 UMAP)。该图像由作者创建。
在我们的论文1中,我们使用了多种数据集来展示如何通过识别可疑细胞和优化超参数来辅助分析。例如,在原始的单细胞 RNA-seq Hydra 数据集2的可视化中,神经元外胚层 1(神经元 ec1)细胞被分成两个簇,一个被 scDEED 标记为可疑,另一个则是可信的(图 2a)。根据基因表达的相似性(图 2c)以及作者分配的单一细胞类型的确认,这两个簇在生物学上并无区别,因此它们在 t-SNE 中的分离是误导性的。此外,如果我们将神经元 ec1 细胞与其邻近的簇进行比较,例如高亮显示的外胚层上皮细胞(ecEP_sc),它们的基因表达差异很大,而这一点在可视化中它们的位置接近时显得直觉上难以理解。然而,在 scDEED 优化后的困惑度下(图 2b),神经元 ec1 细胞类型现在被统一了,进一步证明了最初细胞类型在簇中的分离是超参数设置的结果。此外,神经元 ec1 和 ecEP_sc 细胞现在彼此相距较远,这在基因表达的差异上更加合适。这突出了 scDEED 的两个应用:识别可疑细胞有助于辨别嵌入位置误导的细胞,而优化超参数设置则能得到更可信的可视化结果。
图 2. 在 Hydra 数据集上,scDEED 优化后的 t-SNE 嵌入评估。a–b,对比 t-SNE 图,突出显示了ecEP_sc(外胚层上皮 _ 单细胞)、神经元 ec1 中的可信细胞嵌入和神经元 ec1 中的可疑细胞嵌入,分别在原始困惑度 40(a)和通过 scDEED 优化的困惑度 230(b)下。c,高亮细胞在a和b中的基因表达热图,细胞根据 R 函数 heatmap.2()找到的默认层次聚类顺序排列。该图像由作者创建,并已发表于文章中。
一个有趣的应用是 RNA 速度 3,这是一项依赖于可视化的下游分析任务。RNA 速度利用未剪接和已剪接的 mRNA 数量来估算基因速度——基因表达的变化。估算的基因速度可以用来计算未来时间点的预测基因表达,这可以通过从细胞到细胞预测状态的箭头来进行可视化。对于大规模数据集,逐个绘制每个细胞的速度向量是不现实的;相反,细胞基于其 2D 嵌入进行分组,并将其速度向量进行聚合。对 2D 嵌入的变化不会影响个别细胞的估算基因速度或预测表达,但它会改变细胞分组,从而影响向量场的计算,进而影响可视化的 RNA 速度和分析。使用 scDEED 优化 t-SNE 的超参数困惑度 (图 3a) 大大增强了邻近细胞之间的一致性,并提供了比使用默认超参数值(图 3b)更清晰的 RNA 速度结果。此外,对于成熟颗粒,向量没有被夸大,这是预期的结果,因为细胞已经完全分化。超参数的优化仅增强了现有细胞轨迹。
图 3. 齿状回数据集的速度分析 4. a-b 使用原始 t-SNE 困惑度为 30(a)和通过 scDEED 优化的困惑度 450(b)进行的速度可视化,使用默认的 Velocyto 3 设置。细胞类型的缩写如下:Neuro1:神经母细胞 1;Neuro2:神经母细胞 2;nIPC:神经元中间前体细胞。
最近的研究 [5,6] 强调了几何特性,如测地线、流形和距离,这些特性无法完全重建,因为嵌入前后的空间不是同胚的。scDEED 可以通过找到准确捕捉中等范围细胞间关系的超参数设置,帮助减少不一致性,并识别出那些中等范围邻近关系发生剧烈变化的细胞。我们希望 scDEED 能作为现有分析管道的附加工具,提供更可靠的 2D 可视化。值得指出的是,scDEED 并不衡量数据所有方面的保留;正如制图师认为最重要的是保留五大洲一样,我们选择优先考虑细胞的相对位置。通过对可靠性评分(每个细胞嵌入一个)定义的一些调整,仍有兴趣保留嵌入前空间其他特征的研究人员,可能会发现 scDEED 框架依然具有实用价值。
参考文献
1. Xia L, Lee C, Li JJ. 统计方法 scDEED 用于检测可疑的 2D 单细胞嵌入并优化 t-SNE 和 UMAP 的超参数。Nat Commun. 2024;15: 1753. doi:10.1038/s41467-024-45891-y
2. Siebert S, Farrell JA, Cazet JF, Abeykoon Y, Primack AS, Schnitzler CE, 等. 水螅干细胞分化轨迹在单细胞分辨率下解析。Science. 2019;365. doi:10.1126/science.aav9314
3. La Manno G, Soldatov R, Zeisel A, Braun E, Hochgerner H, Petukhov V, 等. 单细胞的 RNA 速度。Nature. 2018;560: 494–498.
4. Hochgerner H, Zeisel A, Lönnerberg P, Linnarsson S. 通过单细胞 RNA 测序揭示了齿状回神经发生在出生后发育中的保守特性。Nat Neurosci. 2018;21: 290–299.
5. Wang S, Sontag ED, Lauffenburger DA. 在单细胞“组学”数据的二维可视化中,哪些内容无法正确呈现?Cell Syst. 2023;14: 723–731.
6. Chari T, Pachter L. 单细胞基因组学的虚假艺术。PLoS Comput Biol. 2023;19: e1011288.
统计抽样简介
“如果你尝试了,你可能会想买它。”
·发表于Towards Data Science ·阅读时间 28 分钟·2024 年 12 月 11 日
--
统计抽样是一门选择一个能够体现你想研究的人群本质的样本的艺术。因此,一个好的样本是该人群的微缩版。
为了实现这一崇高目标,各种豪华的抽样方法应运而生。
有些方法,如按规模概率抽样、聚类抽样,以及某种程度上的配额抽样,旨在当你意识到资助方希望获得一个代表性极强的样本时,却发现他们的支付能力与之完全脱节时,来拯救你。
然后,还有其他一些方法——比如便利抽样和偶然抽样——它们被命名为一种特殊的方式,用这种方式将那些令人不安的非科学方法包裹在虚假的严谨性和尊严的华丽外衣中。
多年来,人们和企业也创造了个性化的“抽样”概念,从而形成了富有创意的解释。
例如,考虑一下电视剧《人人都爱雷蒙德》(Everybody Loves Raymond)中弗兰克·巴罗恩(Frank Barone)那种挑衅的漫不经心态度(S07E12,“爷爷偷东西”),当他在超市里帮助自己拿一把把的混合坚果时,坚信自己只是在“品尝”而已。
掌握统计检验(第一部分)
选择适合您数据的正确检验的指南
·发表于Towards Data Science ·12 分钟阅读·2024 年 5 月 19 日
--
图片来源:Adam Nowakowski在Unsplash
你是否曾经遇到过一个数据集,结果发现自己对于哪个统计显著性检验最适合回答你的研究问题感到迷茫和困惑?放心,告诉你,你并不孤单。我曾经也是那个人!尽管我尊重统计学,但我并没有对它产生强烈的热情。在这篇文章中,我将重点讲解一些关键概念,帮助你在选择合适的统计显著性检验时做出明智的决定。由于进行统计显著性检验本质上涉及处理变量(自变量和因变量),因此我认为有必要先了解不同类型的这些变量。
数据类型:
图片来源:Claudio Schwarz在Unsplash
1- 分类或名义变量
分类变量(或名义变量)有两个或更多没有内在顺序的类别。例如,眼睛颜色是一个分类变量,其类别包括蓝色、绿色、棕色和榛色。对于这些类别没有公认的排名方式。如果一个变量有明确的顺序,那么它就是一个顺序变量,下面会讨论这一点。
统计确认你的基准测试——通过案例研究比较 Pandas 和 Polars 在 100 万行数据上的表现
使用 Python 比较独立样本 t 检验和 Welch t 检验的基准测试得分
·发表于 Towards Data Science ·13 分钟阅读·2024 年 6 月 18 日
--
图像由 Google Gemini 生成
基准测试是我在互联网上最喜欢的内容之一。不仅看到结果令人兴奋,而且它还帮助我在不浪费时间和金钱的情况下进行比较。例如,比较 GPU 的文章和视频可以指导我下一台应该购买的笔记本电脑。在编程中,基准测试帮助我看到哪些方法运行得更快。
然而,要小心你在互联网上看到的一切,因为你可能会得到完全不同的结果。为什么?
我并不是说发布的分数不真实。这些测试必须在标准且可靠的流程下进行。顺便说一下,问题不在于在其他设备上执行相同的测试;即使是相同配置的设备,结果也可能不同。基准测试的结果可能会受到多种因素的影响(参考 1, 参考 2)。
本文中的一个示例,比较 Pandas 和 Polars 库的执行时间——图像由作者提供。
对于某些硬件和软件测试,如果资源有限,进行实验是很困难的。顺便说一下,对于编程来说,这可能是……
斯坦因悖论
为什么样本均值并不总是最优
·发表于Towards Data Science ·阅读时间:8 分钟·2024 年 9 月 30 日
--
作者插图
平均值是统计学中最基本的工具之一,仅次于计数。虽然它的简单性可能让人觉得直观,但由于其强大的属性,平均值在许多数学概念中扮演着核心角色。概率论中的重要定理,如大数法则和中心极限定理,都强调了平均值不仅仅是方便——它在估计参数时往往是最优的。核心统计方法,如最大似然估计(MLE)和最小方差无偏估计(MVUE),也进一步证明了这一观点。
然而,这一长期以来的信念在 1956 年被颠覆1,当时查尔斯·斯坦因做出了突破,挑战了超过 150 年的估计理论。
历史
平均值一直被认为是估计随机变量分布的集中趋势的有效方法,特别是在正态分布的情况下。正态(或高斯)分布的特点是钟形曲线和两个关键参数:均值(θ)和标准差(σ)。均值表示曲线的中心,而标准差反映了数据的分布范围。
统计学家经常是倒推,通过观察数据来推断这些参数。高斯证明了样本均值最大化了观察数据的似然性,因此它是一个无偏估计量——这意味着它不会系统地高估或低估真实均值(θ)。
统计理论的进一步发展确认了样本均值的实用性,样本均值在与其他线性无偏估计量比较时,能够最小化预期的平方误差。像 R.A. 费舍尔和杰尔日·内曼这样的研究人员通过引入风险函数扩展了这些思想,风险函数用于衡量不同 θ 值下的平均平方误差。他们发现,尽管均值和中位数的风险是恒定的,但均值始终提供较低的风险,证明了其优越性。
然而,斯坦因定理表明,当同时估计三个或更多参数时,样本均值变得不可接受。在这些情况下,偏倚估计量通过提供更低的整体风险,可以优于样本均值。斯坦因的工作彻底改变了统计推断,提升了多参数估计的准确性。
詹姆斯-斯坦因估计量
詹姆斯-斯坦因2估计量是查尔斯·斯坦因发现的悖论中的关键工具。它挑战了样本均值总是最佳估计量的观点,尤其是在同时估计多个参数时。詹姆斯-斯坦因估计量的核心思想是将各个样本均值“收缩”向一个中央值(总均值),从而减少整体估计误差。
为了澄清这一点,我们首先考虑一个向量 x,它表示多个变量的样本均值(不一定独立)。如果我们将这些均值的平均值取出来,我们得到一个单一的值,记作 μ,我们称之为总均值。詹姆斯-斯坦因估计量的工作原理是将每个样本均值向这个总均值靠拢,从而减少它们的方差。
詹姆斯-斯坦因估计量的通用公式3是:
其中:
-
x 是样本均值向量。
-
μ 是总均值(样本均值的平均值)。
-
c 是一个介于 0 和 1 之间的收缩因子。它决定了我们将各个均值拉向总均值的程度。
这里的目标是减少各个样本均值与总均值之间的距离。例如,如果某个样本均值离总均值很远,估计量会将其收缩向中心,从而平滑数据中的变异性。
c 的值,即收缩因子,取决于数据和估计的内容。样本均值向量遵循多元正态分布,因此如果我们试图估计的是这个分布,公式变为:
其中:
-
p 是正在估计的参数个数(即 x 的长度)。
-
σ² 是样本均值向量 x 的方差。
-
项 (p — 2)/||x||² 根据数据的方差和参数的个数调整收缩量。
关键假设和调整
使用 James-Stein 估计量的一个关键假设是方差σ²对所有变量是相同的,但在实际数据中这一假设通常不成立。然而,可以通过标准化数据来缓解这一假设,使得所有变量具有相同的方差。另一种方法是将各个变量的方差平均化为一个合并估计。这种方法在数据集较大时尤其有效,因为随着样本量的增加,方差差异通常会减小。
一旦数据被标准化或合并,收缩因子就可以应用,以适当调整每个样本均值。
选择收缩因子
收缩因子c至关重要,因为它控制样本均值被拉向总体均值的程度。c接近 1 表示几乎没有收缩,类似于常规样本均值的行为。相反,c接近 0 表示显著的收缩,几乎将样本均值完全拉向总体均值。
c的最优值取决于具体数据和估计的参数,但一般的指导原则是,参数越多(即p越大),收缩越有益,因为这减少了对噪声数据的过拟合风险。
在代码中实现 James-Stein 估计量
这里是 James-Stein 估计量在 R、Python 和 Julia 中的函数:
## R ##
james_stein_estimator <- function(Xbar, sigma2 = 1) {
p <- length(Xbar)
norm_X2 <- sum(Xbar²)
shrinkage_factor <- max(0, 1 - (p - 2) * mean(sigma2) / norm_X2)
return(shrinkage_factor * Xbar)
}
## Python ##
import numpy as np
def james_stein_estimator(Xbar, sigma2=1):
p = len(Xbar)
norm_X2 = np.sum(Xbar**2)
shrinkage_factor = max(0, 1 - (p - 2) * np.mean(sigma2) / norm_X2)
return shrinkage_factor * Xbar
## Julia ##
function james_stein_estimator(Xbar, sigma2=1)
p = length(Xbar)
norm_X2 = sum(Xbar.²)
shrinkage_factor = max(0, 1 - (p - 2) * mean(sigma2) / norm_X2)
return shrinkage_factor * Xbar
end
示例
为了展示这一技术的多样性,我将生成一个 6 维的数据集,每一列都包含来自不同随机分布的数值数据。以下是我将使用的每个分布及其参数:
X1 ~ t 分布(ν = 3) X2 ~ 二项分布(n = 10, p = 0.4) X3 ~ Gamma 分布(α = 3, β = 2) X4 ~ 均匀分布(a = 0, b = 1) X5 ~ 指数分布(λ = 50) X6 ~ 泊松分布(λ = 2)
请注意,本数据集中的每一列都包含独立变量,即每一列之间不应存在相关性,因为它们是独立创建的。这并不是使用此方法的必要条件,仅仅是为了简化操作并展示此结果的悖论性质。如果你不完全熟悉这些分布中的任何一个或全部,我会附上一张简单的图,展示每个单变量列的随机生成数据。这仅仅是从上述每个分布中生成的 1,000 个随机变量中的一次迭代。
从上面的直方图中应该可以清楚地看到,并非所有这些变量都遵循正态分布,这意味着整个数据集不是多元正态分布。
由于每个分布的真实情况已知,我们知道每个分布的真实平均值。该多变量数据集的平均值可以以向量形式表示,每行条目代表相应变量的平均值。在此示例中,
知道每个变量的真实平均值将帮助我们衡量样本均值或詹姆斯·斯坦估计器的接近程度,这意味着接近真实值越好。以下是我在 R 代码中进行的实验,该实验生成了 6 个随机变量,并使用均方误差(Mean Squared Error)与真实平均值进行比较。该实验随后进行了 10,000 次,使用了四种不同的样本大小:5、50、500 和 5,000。
set.seed(42)
## Function to calculate Mean Squared Error ##
mse <- function(x, true_value)
return( mean( (x - true_value)² ) )
## True Average ##
mu <- c(0, 4, 1.5, 0.5, 0.02, 2)
## Store Average and J.S. Estimator Errors ##
Xbar.MSE <- list(); JS.MSE <- list()
for(n in c(5, 50, 500, 5000)){ # Testing sample sizes of 5, 30, 200, and 5,000
for(i in 1:1e4){ # Performing 10,000 iterations
## Six Random Variables ##
X1 <- rt(n, df = 3)
X2 <- rbinom(n, size = 10, prob = 0.4)
X3 <- rgamma(n, shape = 3, rate = 2)
X4 <- runif(n)
X5 <- rexp(n, rate = 50)
X6 <- rpois(n, lambda = 2)
X <- cbind(X1, X2, X3, X4, X5, X6)
## Estimating Std. Dev. of Each and Standardizing Data ##
sigma <- apply(X, MARGIN = 2, FUN = sd)
## Sample Mean ##
Xbar <- colMeans(X)
## J.S. Estimator ##
JS.Xbar <- james_stein_estimator(Xbar=Xbar, sigma2=sigma/n)
Xbar.MSE[[as.character(n)]][i] <- mse(Xbar, mu)
JS.MSE[[as.character(n)]][i] <- mse(JS.Xbar, mu)
}
}
sapply(Xbar.MSE, mean) # Avg. Sample Mean MSE
sapply(JS.MSE, mean) # Avg. James-Stein MSE
从所有 40,000 次试验中,通过运行最后两行代码计算了每个样本大小的总平均 MSE。每个结果可以在下表中看到。
这次模拟的结果显示,詹姆斯·斯坦估计器在使用 MSE 时始终优于样本均值,但这种差异随着样本大小的增加而减小。
结论
詹姆斯·斯坦估计器展示了一个估计的悖论:通过结合看似独立的变量的信息,竟然能够改善估计。虽然在样本量较大时,MSE 的差异可能微不足道,但这一结果在首次提出时引发了广泛的争议。这一发现标志着统计理论的一个关键转折点,并且在今天的多参数估计中仍然具有重要意义。
如果你想进一步探索,请查看关于斯坦因悖论的这篇详细文章以及写作本文档时引用的其他资料。
参考文献
1 Stein, C. (1956). 多元正态分布均值的常用估计量不可接受性。第三届伯克利数学统计与概率研讨会论文集,1,197–206。
2 Stein, C. (1961). 带有二次损失的估计。在 S. S. Gupta & J. O. Berger(编),统计决策理论与相关话题(第 1 卷,第 361–379 页)。学术出版社。
3 Efron, B., & Morris, C. (1977). 斯坦因悖论在统计学中的应用。科学美国人,236(5),119–127
逐步基础:代码自动文档生成
使用 Sphinx 生成完美的 Python 代码文档
·发表于 Towards Data Science ·阅读时间:9 分钟·2024 年 3 月 22 日
--
图片来源:Dustin Humes 通过 Unsplash
你可以仅通过几步简单的操作,利用 docstring 构建美观、标准化和风格化的文档。
数据科学家在项目中承担着许多职责,其中一个通常会被拖延到最后的就是文档编写。也许你很勤奋地为类和函数编写了 docstring(做得好!)——但这应该是文档的最终形式吗?
在我看来,文档应该独立于代码存在。你的团队(或几个月后的你)不应该需要翻阅 Python 模块中的成百上千行代码才能理解其中的内容。你可以仅通过几步简单的操作,利用 docstring 构建美观、标准化和风格化的文档,让你的项目自我表达。
在本文中,我将重点介绍如何使用 Sphinx 框架 来自动生成 Python 模块的文档。我还将使用 Cookiecutter Data Science 项目模板,并在 Visual Studio Code (VS Code) 中进行,因为它与 Sphinx 的无缝集成以及标准化的目录结构。虽然 官方 Sphinx 教程文档 是希望深入学习这一主题的绝佳资源,但本文的目标是提供一个帮助你快速入门的指南,带你完成关键步骤。祝你阅读愉快 😃
关于 docstring 的说明
良好的文档的关键是 docstring。这些是每个类、类方法和函数中的注释块,用于描述代码的性质,以及输入、输出和引发的错误。有三种核心的 docstring 格式,它们分别是 Google、reStructuredText (reST) 和 NumPy。它们包含相同的信息,唯一的区别是格式不同。你可以在这里查看每种 docstring 格式的示例。
我将使用 Google docstring 格式,因为它比其他格式更易于阅读且占用空间更小。下面的代码块是一个典型的 Google docstring 示例:
"""Description of the function, class or method etc.
Args:
varA (str): Description of varA
varB (bool): Description of varB
Returns:
list: Description of returned list
Raises:
ValueError: Description of raised error
"""
顶级技巧:在 VS Code 中下载‘autoDocstring — Python Docstring 生成器’,当你输入三个双引号(即 docstring 开始符号)时,它将自动生成 docstring。记得在生成 docstring 之前先完成函数编写,这样所有输入/输出/错误都会包含在为你生成的 docstring 模板中!
让我们开始编写文档吧!
为了演示的目的,我创建了一个名为 demo.py
的 Python 模块,其中包含一个类和三个基本函数(除了一个函数,其他都注释了 docstring)。就是这个模块,我将在本文中为其编写文档。以下是 demo.py 模块的内容:
demo.py 模块的内容需要记录。使用 VS Code 中的 CodeSnap 扩展截图。
1. 设置
首先,设置好一切。你需要安装 VS Code 并设置一个新项目,以及安装 Sphinx。这里有几个选项。你可以选择 a) 使用 Cookiecutter 设置一个新项目(在此过程中会自动生成相关的 Sphinx 设置和标准化目录),或者 b) 创建你自己的项目结构并单独安装 sphinx。
选项 A — 安装 Cookiecutter
在终端中,执行 pip install Cookiecutter,然后创建一个新项目:
pip install cookiecutter
cookiecutter https://github.com/drivendata/cookiecutter-data-science
接下来,回答终端窗口中出现的问题,你的新项目将会被创建。Sphinx 框架将存储在项目的 /docs 目录中。
选项 B — Sphinx 快速启动
如果 Cookiecutter 模板不符合你的需求,你可以从头开始创建自己的项目结构并安装 sphinx。最好在一个文档目录中安装 sphinx。可以在终端中执行:
mkdir docs
cd docs
pip install sphinx
sphinx-quickstart
2. 理解 Sphinx 文件夹结构
在你通过上述方式安装了 Sphinx 之后,项目中的文档目录下会出现一些文件。conf.py
文件是关键的配置文件,你需要编辑它以定制你的文档 —— 详细内容将在下一节介绍。index.rst
文件作为文档的目录。你可以在这里找到关于 index.rst
文件的更多信息。getting-started.rst
和 commands.rst
文件是推荐的文档模板。如果需要,你可以删除这些文件。make
文件(make.bat
和 Makefile
)用于实际生成文档。你无需编辑这些文件,但在准备好生成文档时会在终端窗口调用它们。
默认安装的 Sphinx 文件
3. Conf.py 文件
配置文件是魔法发生的地方。这个文件在构建过程中使用,因此确保正确设置它至关重要。以下是修改 conf.py
文件的一些步骤:
取消注释 sys.path 行(我的设置中的第 20 行):
# sys.path.insert(0, os.path.abspath('.'))
更改 os.path.abspath 的路径,使其指向你想要记录的代码的相对位置(相对于 conf.py
文件)。例如,我想要记录的 Python 模块位于我项目的 src/
目录中。因此,我会将 os.path.abspath 更改为指向位于 conf.py
文件父文件夹中的 /src
目录。你可以使用 .
和 /
语法指定相对位置:
sys.path.insert(0, os.path.abspath('../src'))
"""
# you can use the following syntax to specify relative locations:
'.' # current path of conf.py
'..' # parent path of conf.py
'../..' # parent of the parent path of conf.py
"""
文档文件夹中包含 Python 模块的目录的相对位置。在这个示例中,‘demo.py’ 是要记录的模块,位于 src/data/
目录中。
添加相关扩展。你需要在 conf.py
文件中添加一些扩展,以便在创建文档时获得额外的功能。这些扩展是可选的,你可以在这里探索可用的不同扩展。我建议至少添加以下五个扩展:
-
sphinx.ext.autodoc— 使用来自文档字符串的文档
-
autodocsumm— 通过仅列出文档字符串摘要来在 HTML 页面的顶部生成一个表格汇总。当你有很多文档字符串时非常有用。注意:你需要在终端中使用 pip 安装 autodocsumm。
-
sphinx.ext.napoleon — 使 Sphinx 能够解析 Google 风格的文档字符串
-
sphinx.ext.viewcode — 为每个模块添加一个链接,指向包含源代码的 HTML 页面
-
sphinx.ext.coverage — 提供类/函数等有多少包含文档字符串的总结。良好的覆盖率表明代码库得到了很好的解释。
下面是如何在 conf.py
文件中包含这些扩展(在我的设置中是第 29 行):
# add in the extension names to the empty list variable 'extensions'
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.napoleon',
'autodocsumm',
'sphinx.ext.coverage'
]
# add in this line for the autosummary functionality
auto_doc_default_options = {'autosummary': True}
更改主题。文档的默认主题相当简洁,尽管你可能更喜欢通过将 'html_theme' 变量(在我的设置中是第 94 行)从 'default' 更改为标准的 主题选项 或一些 第三方选项来尝试不同的选项。在这个示例中,我将展示默认主题和“Read the Docs”主题。
html_theme = 'sphinx_rtd_theme' # read the docs theme. This variable is 'default' by default.
注意:你需要通过 pip 安装任何非标准(第三方)主题。
4. 生成 html 页面
现在你的 conf.py
文件已经设置完毕,且代码中有了精彩的文档字符串,我们可以开始抓取并生成一些 html 页面了。
生成 Python 包的 .rst 文件
这些文件是 html 页面的前置文件,是 Sphinx 的本地格式。在生成 html 文件之前,需要先生成这些文件。你将使用 sphinx.apidoc 命令,它利用 autodoc 扩展来定位你在 conf.py
文件中指定的 sys.path 位置下的所有 Python 模块(例如任何 .py 文件)。在使用 apidoc 命令时,有一些可选参数可以包含,详情请参阅 文档,但我使用了以下模板:
注意:在终端中,切换到项目根目录以运行以下代码。
sphinx-apidoc -f -o output_dir module_dir/
-f(强制覆盖任何已生成的文件)。
-o output_dir(放置输出文件的目录。如果该目录不存在,将会创建)。注意:将 'output_dir' 替换为你选择的目录名称。我将我的设置为 /docs 目录。
module_dir(Python 包的位置,用于文档化)
运行该命令后,docs 文件夹中应该会生成新的 .rst 文件。
运行 sphinx-apidoc 命令生成 .rst 文件后,文档文件夹的内容
请注意,已经生成了两个新的 .rst 文件:data.rst
和 modules.rst
。除了 modules.rst
,对于每个包含至少一个 Python 模块的目录,将会生成一个 .rst 文件。在我的示例中,由于我将 demo.py 文件保存在 src/data 目录中,因此生成了 data.rst
。如果在你指定的 conf.py
文件中的 sys.path 位置包含多个 Python 模块目录,那么将生成多个 .rst 文件。注意:这些文件目前还不包含抓取的文档,它们只包含 autodoc 所需的信息,以便在下一步生成 html 文件。
编辑 index.rst 文件
请记住,index.rst
作为目录页,因此我们必须编辑此文件,以包括我们希望文档化的所有 Python 模块。幸运的是,modules.rst
引用了在 sys.path
中识别到的所有 Python 模块的源位置,因此你可以简单地将此文件添加到 index.rst
。
为此,打开 index.rst
文件,并在 toctree(目录树)部分下方添加‘modules’。确保在 :maxdepth: 参数和 .rst 文件名称之间有一行空白。
注意。‘getting-started’ 和 ‘commands’ 将已经出现在 index.rst
文件中。如果你不希望生成 HTML 页面,可以将它们从文件中删除(尽管 ‘getting-started’ 页面可能是个好主意!)
index.rst
文件的内容。我已添加‘modules’,以便在 HTML 生成过程中使用 modules.rst
文件。
创建 HTML 文件
现在我们可以使用文档目录中的 make 文件来构建 HTML 文件。这些文件将出现在文档文件夹中的 _build/html/ 目录下。如果你下载了‘HTML Preview’扩展程序,可以在 VS Code 中预览这些文件。
切换到 make.bat
文件所在的目录,并在 cmd 终端中运行以下命令:
make html
注意。如果你使用的是 Windows PowerShell 终端(而非 cmd),请使用以下语法:
.\make.bat html
顶级提示。如果在使用 make html
命令时出现警告,提示 ‘autodoc: failed to import module’,这很可能是因为 autodoc 无法找到你的模块,因为 sys.path
在 conf.py
中未正确配置。确保它指向你的 Python 模块所在的目录。*
编辑 HTML 文件
如果你希望编辑文档字符串并将更改更新到 HTML 文件中,可以使用以下命令:
make clean html
让我们来看看我们的文档!
如我之前提到的,我已经为我的 Python 模块 demo.py
创建了两种不同主题的文档,如下图所示;‘默认’(左侧图片)和‘Read the Docs’(右侧图片)。内容是相同的,但外观和感觉不同。让我们注意一下核心特点:
-
左侧的导航栏
-
模块所有类或函数的摘要,以表格形式显示在页面顶部(感谢 ‘autodocsumm’ 扩展)
-
所有函数和类的文档字符串组件的详细列表,位于总结下方
使用默认主题(左侧图片)和 Read the Docs 主题(右侧图片)的 Python 模块文档页面示例,通过 Sphinx 生成。
一旦你创建了 html 页面,你会注意到会生成多种层次的 html 页面。这些页面包括主页以及每个包和模块的页面。浏览这些页面,熟悉它们的结构,并阅读官方文档,了解如何进一步自定义它们。
例如,如果你希望在文档中查看每个函数的原始代码,可以将扩展 ‘sphinx.ext.viewcode’ 添加到 conf.py
文件中。这将为每个函数或类方法添加一个超链接,点击后可以显示原始代码,方便查看,无需深入代码库。
结语
就这样。通过几个简单的步骤使用 Sphinx 创建的 Python 模块文档,既简单又美观!希望你在了解自动化文档生成的过程中有所收获,并认为它是一个值得在项目中实现的有用工具。如果你有任何有用的建议,欢迎留言评论 😃
在 Plotly 中构建排名图的逐步指南
学习如何使用 Plotly 在 Python 中创建自定义排名图进行数据可视化
·发表于 Towards Data Science ·阅读时间:11 分钟·2024 年 12 月 4 日
--
Plotly 中的排名图(图片来源:作者)
Plotly 是 Python 中最完整的数据可视化库之一,毫无疑问,它是我最喜欢的库。它拥有广泛的已定义可视化,从基本的可视化,如条形图或饼图,到更具特定性的统计或数据科学领域的可视化,如箱线图或树状图。
Plotly 提供的可视化选项非常丰富;然而,某些可视化在库中并未提供。这并不意味着我们无法实现它们。通过一些创造力,利用 Plotly 中的自定义和可视化选项,实际上是可以创建许多原本看似不可能实现的可视化。排名图就是其中之一。
本文将解释如何使用 Plotly 创建排名图。从散点图开始,借助一些想象力和创造力,我们会发现创建这种类型的可视化比看起来更容易。
1. 为什么选择排名图?
排名图,也称为排名曲线图,旨在探索随时间变化的排名。这种图表可以帮助你快速识别趋势……
Plotly 中交互式日历的逐步构建指南
使用 Plotly 创建带热图的交互式日历
·发表于 Towards Data Science ·阅读时长 7 分钟·2024 年 8 月 16 日
--
Debby Hudson 在 Unsplash
Plotly 是最全面的 Python 可视化库之一。这个库提供了许多预定义的可视化工具,适用于大多数数据分析。这些可视化工具高度可定制,使我们能够更具创造性地展示我们的见解,甚至实现新的可视化效果。
在本文中,我们将逐步讲解如何使用 Plotly 创建一个日历,展示巴塞罗那 2024 年的所有假期。该日历将具有交互性,通过悬停提示提供有关城市中不同假期的信息。正如文章所示,尽管 Plotly 没有内置的日历可视化工具,但从热图开始,稍加想象,这种类型的可视化可以轻松实现。
Plotly 中的日历相比预定义工具所创建的日历具有更高的自定义性。此外,一个主要优势是,它们可以集成到 Jupyter 笔记本中,例如,可以在这些笔记本中进行时间分析。此外,悬停提示可以根据我们的分析需求进行自定义。与预定义工具相比,另一个重要的优势是 Plotly 完全免费……
步骤指南:如何在 Plotly 中构建 Waffle 图表
学习如何使用 Plotly 在 Python 中创建自定义 Waffle 图表进行数据可视化
·发布于 Towards Data Science ·11 分钟阅读·2024 年 11 月 25 日
--
Waffle 图表在 Plotly 中(图像来源:作者)
Plotly 是一个功能最为全面的 Python 数据可视化库之一,毫无疑问,也是我最喜欢的库。它已经定义了广泛的可视化方式,从基本的图表(如柱状图或饼图)到更具体的统计学或数据科学领域的可视化(如箱线图或树状图)。
Plotly 提供的可视化选项非常广泛;然而,某些可视化在库中并不存在。这并不意味着我们无法创建这些可视化。通过一点巧思,结合 Plotly 提供的定制和可视化选项,我们可以创造出许多在表面上看似不可能实现的可视化,其中之一就是 Waffle 图表。
本文将解释如何使用 Plotly 创建 Waffle 图表。从热图开始,通过一点想象力和创造力,我们将看到创建这种类型的可视化其实比看起来更简单。
1. 为什么选择 Waffle 图表?
Waffle 图表是饼图或柱状图的一个有趣替代方案,当你想用另一种布局展示比例时,它非常合适…
Python 中创建模拟数据的分步指南
一份适合初学者的教程,教你生成自己的数据以便进行分析和测试
·发布于 Towards Data Science ·7 分钟阅读·2024 年 7 月 10 日
--
图片来源:Alexandru-Bogdan Ghita 在 Unsplash
想象一下,你刚刚编写了一个机器学习模型,需要在特定场景下进行测试,或者你正在发布一篇关于自定义数据科学解决方案的学术论文,但现有数据集存在版权限制。另一方面,你可能正处于机器学习项目的调试和故障排除阶段,需要数据来识别和解决问题。
所有这些情况,以及更多情境,都可以从使用模拟数据中获益。通常,真实世界的数据并不容易获得,价格昂贵,或者具有隐私限制。因此,创建合成数据是数据科学从业者和专业人士的一项有用技能。
在本文中,我介绍了几种创建模拟数据、玩具数据集和“虚拟”值的方法和技巧,这些都是使用 Python 从零开始创建的。一些解决方案使用了 Python 库中的方法,另一些则是使用 Python 内置函数的技术。
接下来的各个方法在我进行研究任务、学术论文撰写、模型训练或测试时都曾对我非常有帮助。希望读者能在文章结尾探索这个笔记本,并将其作为指南,或者作为未来项目的参考。
目录
1. 使用 NumPy
2. 使用 Scikit-learn
3. 使用 SciPy
4. 使用 Faker
5. 使用合成数据库 (SDV)
结论与下一步
1. 使用 NumPy
最著名的 Python 库之一,专注于线性代数和数值计算的 SciPy,同样也有助于数据生成。
- 线性数据生成
在这个示例中,我将展示如何创建一个带噪声且与目标值具有线性关系的数据集。这对于测试线性回归模型非常有用。
# importing modules
from matplotlib import pyplot as plt
import numpy as np
def create_data(N, w):
"""
Creates a dataset with noise having a linear relationship with the target values.
N: number of samples
w: target values
"""
# Feature matrix with random data
X = np.random.rand(N, 1) * 10
# target values with noise normally distributed
y = w[0] * X + w[1] + np.random.randn(N, 1)
return X, y
# Visualize the data
X, y = create_data(200, [2, 1])
plt.figure(figsize=(10, 6))
plt.title('Simulated Linear Data')
plt.xlabel('X')
plt.ylabel('y')
plt.scatter(X, y)
plt.show()
模拟线性数据(图片由作者提供)。
- 时间序列数据
在这个示例中,我将使用 NumPy 生成具有线性趋势和季节性成分的合成时间序列数据。这个示例对金融建模和股市预测非常有用。
def create_time_series(N, w):
"""
Creates a time series data with a linear trend and a seasonal component.
N: number of samples
w: target values
"""
# time values
time = np.arange(0,N)
# linear trend
trend = time * w[0]
# seasonal component
seasonal = np.sin(time * w[1])
# noise
noise = np.random.randn(N)
# target values
y = trend + seasonal + noise
return time, y
# Visualize the data
time, y = create_time_series(100, [0.25, 0.2])
plt.figure(figsize=(10, 6))
plt.title('Simulated Time Series Data')
plt.xlabel('Time')
plt.ylabel('y')
plt.plot(time, y)
plt.show()
- 自定义数据
有时需要具有特定特征的数据。例如,你可能需要一个高维数据集,其中只有少数几个维度是有意义的,适合用于降维任务。在这种情况下,下面的示例展示了生成此类数据集的合适方法。
# create simulated data for analysis
np.random.seed(42)
# Generate a low-dimensional signal
low_dim_data = np.random.randn(100, 3)
# Create a random projection matrix to project into higher dimensions
projection_matrix = np.random.randn(3, 6)
# Project the low-dimensional data to higher dimensions
high_dim_data = np.dot(low_dim_data, projection_matrix)
# Add some noise to the high-dimensional data
noise = np.random.normal(loc=0, scale=0.5, size=(100, 6))
data_with_noise = high_dim_data + noise
X = data_with_noise
上面的代码片段创建了一个包含 100 个观察值和 6 个特征的数据集,基于一个仅有 3 个维度的低维数组。
2. 使用 Scikit-learn
除了机器学习模型,Scikit-learn 还有一些数据生成器,用于构建具有可控大小和复杂度的人工数据集。
- 制作分类数据集
make_classification 方法可以用来创建一个随机的多类别数据集。该方法允许根据选择的观察值、特征和类别数来创建数据集。
它对于测试和调试分类模型(如支持向量机、决策树和朴素贝叶斯)也非常有用。
X, y = make_classification(n_samples=1000, n_features=5, n_classes=2)
#Visualize the first rows of the synthetic dataset
import pandas as pd
df = pd.DataFrame(X, columns=['feature1', 'feature2', 'feature3', 'feature4', 'feature5'])
df['target'] = y
df.head()
数据集的前几行(图片由作者提供)。
- 制作回归数据
类似地,make_regression 方法对于创建回归分析数据集非常有用。它允许设置观察值的数量、特征数量、偏差以及生成数据集的噪声。
from sklearn.datasets import make_regression
X,y, coef = make_regression(n_samples=100, # number of observations
n_features=1, # number of features
bias=10, # bias term
noise=50, # noise level
n_targets=1, # number of target values
random_state=0, # random seed
coef=True # return coefficients
)
使用make_regression生成的模拟数据(图片由作者提供)。
- 制作簇数据
make_blobs 方法允许创建人工的“簇”数据,这些数据可用于聚类任务。它允许设置数据集中的总点数、簇的数量以及簇内的标准差。
from sklearn.datasets import make_blobs
X,y = make_blobs(n_samples=300, # number of observations
n_features=2, # number of features
centers=3, # number of clusters
cluster_std=0.5, # standard deviation of the clusters
random_state=0)
聚类中的模拟数据(图片由作者提供)。
3. 使用 SciPy
SciPy(科学 Python)库与 NumPy 一起,是处理数值计算、优化、统计分析和许多其他数学任务的最佳库之一。SciPy 的 stats 模型可以根据多种统计分布(如正态分布、二项分布和指数分布)创建模拟数据。
from scipy.stats import norm, binom, expon
# Normal Distribution
norm_data = norm.rvs(size=1000)
图片由作者提供。
# Binomial distribution
binom_data = binom.rvs(n=50, p=0.8, size=1000)
图片由作者提供。
# Exponential distribution
exp_data = expon.rvs(scale=.2, size=10000)
图片由作者提供。
4. 使用 Faker
那么,非数值数据呢?我们通常需要对非数值或用户数据(如姓名、地址和电子邮件)进行训练。一个创建与用户信息相似的现实数据的解决方案是使用 Faker Python 库。
Faker 库可以生成逼真的数据,用于测试应用程序和机器学习分类器。在下面的示例中,我展示了如何创建一个包含姓名、地址、电话号码和电子邮件信息的假数据集。
from faker import Faker
def create_fake_data(N):
"""
Creates a dataset with fake data.
N: number of samples
"""
fake = Faker()
names = [fake.name() for _ in range(N)]
addresses = [fake.address() for _ in range(N)]
emails = [fake.email() for _ in range(N)]
phone_numbers = [fake.phone_number() for _ in range(N)]
fake_df = pd.DataFrame({'Name': names, 'Address': addresses, 'Email': emails, 'Phone Number': phone_numbers})
return fake_df
fake_users = create_fake_data(100)
fake_users.head()
使用 Faker 生成的假用户数据(作者提供的图片)。
5. 使用 Synthetic Data Vault(SDV)
如果你有一个数据集,其中的观测数据不足,或者你需要更多与现有数据集相似的数据来补充机器学习模型的训练步骤,该怎么办?Synthetic Data Vault(SDV)是一个 Python 库,它通过统计模型创建合成数据集。
在下面的示例中,我们将使用 SDV 扩展一个演示数据集:
from sdv.datasets.demo import download_demo
# Load the 'adult' dataset
adult_data, metadata = download_demo(dataset_name='adult', modality='single_table')
adult_data.head()
成人演示数据集。
from sdv.single_table import GaussianCopulaSynthesizer
# Use GaussianCopulaSynthesizer to train on the data
model = GaussianCopulaSynthesizer(metadata)
model.fit(adult_data)
# Generate Synthetic data
simulated_data = model.sample(100)
simulated_data.head()
模拟样本(作者提供的图片)。
请观察这些数据与原始数据集非常相似,但它们是合成数据。
结论与下一步
文章展示了创建模拟和合成数据集的 5 种方法,这些数据集可以用于机器学习项目、统计建模以及其他涉及数据的任务。文中展示的示例易于跟随,因此我建议你深入探索代码,阅读相关文档,并开发出更适合每个需求的数据生成方法。
如前所述,数据科学家、机器学习专家和开发人员可以通过使用合成数据集来提高模型性能,降低生产和应用测试的成本。
查看本文中探讨的所有方法的笔记本:
[## GitHub - Marcussena/Synthetic-data-generation: 数据科学和机器学习的模拟数据生成
数据科学和机器学习的模拟数据生成 - Marcussena/Synthetic-data-generation
参考资料
1 DataCamp. “使用 Python 和 Faker 创建合成数据。” DataCamp, www.datacamp.com/tutorial/creating-synthetic-data-with-python-faker-tutorial
. 访问日期:2024 年 7 月 4 日。
2 Scikit-learn. “生成的数据集。”Scikit-learn, scikit-learn.org/stable/datasets/sample_generators.html#sample-generators
. 访问日期:2024 年 7 月 4 日。
3 SDV 用户指南。“高斯 Copula 用户指南。” SDV, sdv.dev/SDV/user_guides/single_table/gaussian_copula.html
。访问时间:2024 年 7 月 4 日。
4 SciPy 用户指南。“SciPy 教程。” SciPy 文档, docs.scipy.org/doc/scipy/tutorial/index.html
。访问时间:2024 年 7 月 4 日。
使用 Plotnine 进行时间序列可视化的逐步指南
探索时间序列的 6 种图形
·发表于Towards Data Science ·7 分钟阅读·2024 年 3 月 27 日
--
可视化是一种快速且有效的方式,从数据中获取洞察。本文提供了一个逐步指南,用于使用图形探索时间序列。
我们将使用 6 种不同的图表来揭示时间序列的不同方面。我们将重点介绍 Python 的 plotnine,这是一个图形语法类型的库。
介绍
探索性数据分析是一种旨在揭示数据集底层结构的方法。几乎总是,这个过程涉及使用图形技术来可视化数据。
使用图形进行时间序列分析是一种快速从数据中提取洞察的方法,例如:
-
发现基本模式,如趋势或季节性
-
检测异常,包括缺失数据或离群值
-
检测分布的变化
在本文的其余部分,您将学习如何构建 6 种图形来探索时间序列。
探索时间序列
让我们从加载一个时间序列开始。在本指南中,我们将使用 M3 数据集2中的一个月度时间序列。我们…
领域适应简介——动机、选择、权衡
脱离“舒适区”——LLM 领域适应方法的深度探讨(第一部分)
·发布于 Towards Data Science ·12 分钟阅读·2024 年 5 月 28 日
--
图片由 StableDiffusionXL 提供,托管于 Amazon Web Services
探索将大型语言模型(LLMs)适应到你的特定领域或应用场景?这篇三部分博客系列解释了领域适应的动机,并深入探讨了各种实现方法。此外,还将提供一份详细指南,帮助你掌握整个领域适应的过程,涵盖了流行的权衡问题。
第一部分:领域适应简介——动机、选择、权衡 ——你现在正阅读这一部分!第二部分:深入探讨上下文学习**第三部分:深入探讨微调
注意:除非另有说明,所有图片均由作者提供。
这是什么内容?
生成性人工智能迅速吸引了全球的关注,随着 Claude3、GPT-4、Meta LLaMA3 或 Stable Diffusion 等大型语言模型展示了在内容创作上的新能力。这些模型可以生成极具人类特点的文本、图像等,激发了人们的热情,但也引发了对潜在风险的担忧。虽然个人热衷于试验展示这项新兴技术的应用,但组织也在寻求战略性地利用它。
图 1:“没有人是完美的”——随着我们走出系统的“舒适区”,智能系统的表现逐渐下降
在谈到人工智能(AI)模型和智能系统时,我们本质上是在尝试使用数学/统计概念和由强大计算机系统支持的算法来逼近人类水平的智能。然而,这些 AI 模型并不完美——重要的是要认识到它们有固有的局限性和“舒适区”,就像人类一样。模型在其能力范围内擅长某些任务,但当被推到其隐喻性“舒适区”之外时,就会遇到困难。可以这样理解——我们每个人都有一块自己非常擅长且感到舒适的任务和活动区域。当在这个区域内操作时,我们的表现是最优的。但当面临远超我们专业领域和经验的挑战时,我们的能力开始下降。AI 系统也是如此。
在理想的世界里,我们可以始终部署为特定任务量身定制的正确 AI 模型,使其始终处于舒适区内。但现实世界是复杂和不可预测的。作为人类,我们不断遇到把我们推向舒适区之外的情境——这是生活中不可避免的一部分。AI 模型也面临着同样的挑战。这可能导致模型的响应质量低于预期,可能会导致以下行为:
图 2:无助的模型行为——来源:google/gemma-7b via HuggingFace hub 推理 API
图 2 显示了一个例子,在这个例子中,我们要求一个模型帮助设置广告活动。生成型语言模型被训练成基于概率分布以自回归、下一个标记预测的方式生成文本。虽然上述例子中的模型输出可能符合模型为之优化的训练目标,但对于用户及其预定任务来说,这并不有帮助。
图 3:模型幻觉——来源:Anthropic Claude 3 via Amazon Bedrock
图 3 显示了一个例子,在这个例子中,我们询问了一个模型关于我的问题。显然,关于我的信息并不是模型预训练数据的重要部分,所以模型给出了一个有趣的答案,但不幸的是,这个答案根本不真实。模型出现幻觉,给出了一个不诚实的回答。
图 4:有害的模型行为——来源:Bai 等,2022
模型在大量文本数据上进行训练,包括大量的网络抓取数据。由于这些内容几乎没有经过筛选或整理,模型可能会生成潜在的有害内容,正如上述例子所示(并且可能更糟)。(图 4)
为什么这很重要?
与个人使用的实验相比(在某种程度上可能是可以接受的),如上所示,非确定性和可能有害或有偏见的模型输出——这是由于任务涉及到模型舒适区之外的领域——对企业采用提出了挑战,必须克服这些挑战。当朝这个方向发展时,需要考虑多种维度和设计原则。除了包括上述提到的维度作为设计原则外,通常称为“三个 H”,证明对创建符合企业级要求且合规的生成式 AI 驱动的应用程序非常有益。这些原则包括:
图 5:企业级生成式 AI 驱动应用程序的“三个 H”
-
有用性 — 在组织中使用 AI 系统,如聊天机器人时,必须记住,工作场所的需求远比个人使用要复杂。制定烹饪食谱或写婚礼祝词与构建一个能够帮助整个公司员工的智能助手是截然不同的。对于商业用途,生成式 AI 驱动的系统必须与现有的公司流程对接,并与公司的风格相匹配。它可能需要该公司专有的信息和数据,这些是公开的 AI 训练数据集基础模型所无法涵盖的。此外,系统还必须与内部软件应用程序及其他数据/信息池进行集成。再者,它需要以定制化的方式服务于多种类型的员工。弥合个人使用 AI 与企业级应用之间的巨大差距意味着要专注于有用性,并将系统与组织的具体需求紧密结合。AI 不应采取一刀切的方法,而是需要针对每个商业环境进行深思熟虑的设计,才能成功应对复杂的工作场所需求。
-
诚实性 — 生成型 AI 模型面临幻觉的风险。从实际意义上讲,这意味着这些模型——无论在哪种形式下——可能非常自信地生成包含完全不真实的事实内容。这可能对在专业环境中使用生产级解决方案的用例产生严重影响:如果一家银行为其客户构建聊天机器人助手,而客户询问账户余额时,客户期待得到一个精确且正确的答案,而不是随便给出一个数字。这种行为源于这些模型的概率性质。例如,大型语言模型通常是通过下一个词预测任务进行预训练的。这包括语言学概念、特定语言及其语法的基本知识,也包括训练数据集中隐含的事实知识。由于预测结果是概率性质的,因此无法保证一致性、确定性和信息内容。尽管由于语言本身的模糊性,这通常对与语言相关的方面影响较小,但在处理事实知识时,仍可能对性能产生显著影响。
-
无害性 — 必须采取严格的预防措施,以防止生成型 AI 系统对人类或社会系统及价值观造成任何形式的伤害。必须全面评估并尽可能最大程度地降低诸如偏见、不公平、排斥、操控、煽动、隐私侵犯和安全威胁等潜在风险。遵循伦理原则和人权应当是最重要的。这意味着要使模型本身与这些行为对齐,并在这些模型及上下游应用中设置防护措施,安全和隐私问题也应作为任何软件应用中的首要问题来对待。
尽管这远不是唯一可以用来设计符合这些设计原则的生成型 AI 应用程序的方法,领域适配在研究和实践中已经证明是实现这一目标的一个非常有效的工具。将领域特定信息融入事实知识、任务特定行为以及与治理原则的对齐,已成为一种先进的方法,越来越多的研究表明,它是成功构建生产级生成型 AI 应用程序的关键差异化因素,并且能够在组织中规模化地带来业务影响。成功掌握这一路径对于组织在向 AI 驱动的业务转型过程中至关重要。
本系列博客将深入探讨领域适配技术。首先,我们将讨论提示工程和微调,这是领域适配的不同选项。然后,我们将讨论选择适当适配技术时需要考虑的权衡。最后,我们将详细探讨这两种选项,包括数据视角、低层次实现细节、架构模式和实际示例。
领域适配方法克服“舒适区”限制
回到之前提到的模型“舒适区”的比喻,领域适配是我们选择的工具,用于将表现不佳的任务(红色圆圈)重新带回模型的舒适区,从而使其能够超过预期标准进行表现。为实现这一目标,有两个选择:要么解决任务本身,要么扩展“舒适区”:
方法 1——上下文学习:通过外部工具帮助任务回到舒适区
图 6:通过上下文学习进行领域适配意味着任务向模型的“舒适区”转变
第一个选择是利用外部工具修改任务的解决方式,使其回到(或更接近)模型的舒适区。在大规模语言模型(LLMs)的世界中,这可以通过提示工程来完成,提示工程基于上下文学习,通过注入源知识来转变任务的整体复杂性。它可以以一种静态方式执行(例如,少量提示),但更复杂、动态的提示工程技术,如 RAG(检索增强生成)或智能代理,已被证明非常强大。
但是上下文学习是如何工作的呢?我个人认为这个术语本身非常具有误导性,因为它暗示模型会“学习”。实际上,模型并没有学习;相反,我们是在转变任务的解决方式,目标是减少任务的整体复杂性。通过这样做,我们会更接近模型的“舒适区”,从而提高模型作为一个概率性系统在任务上的平均表现。下面的示例通过提示 Claude 3 来介绍我自己,清晰地展示了这种行为。
图 7:使用上下文学习克服幻觉——来源:Claude 3 Sonnet,来自 Amazon Bedrock
在图 7 中,左侧的示例是幻觉,如我们在前文(图 3)中所述。右侧的示例展示了以单次示例(one-shot)形式的上下文学习——我只是简单地将我的演讲者简介作为上下文添加进去。模型突然表现得非常出色,给出了一个诚实且因此可接受的答案。虽然模型并没有真正“学习”,但我们已经将待解决的任务从我们所说的“开放问答”任务转换为所谓的“封闭问答”任务。这意味着,模型不再需要从权重中提取事实性正确的信息,而是将任务转化为类似信息提取的性质——这种任务的复杂性(对于我们人类来说)显著较低。这就是所有上下文/(动态)提示工程技术的基本概念。
方法 2——微调:利用经验学习扩展模型的“舒适区”以适应任务
图 8:通过微调进行领域适应意味着将模型的“舒适区”扩展到一个或多个任务
第二种选择是通过应用经验学习,针对模型的“舒适区”进行调整。我们人类不断且无意识地利用这一技巧,部分适应变化的环境和需求。迁移学习的概念同样在 LLM 的世界中开启了这扇大门。其思想是利用一个小型的(与模型预训练相对)特定领域数据集,并在基础模型上进行训练。这种方法叫做微调,并且可以以多种方式执行,正如我们将在后续的微调深入探讨中讨论的那样。与上下文学习不同,这种方法现在涉及并更新了模型参数,因为模型在学习适应新领域的过程中。
选择哪个选项?
图 9:领域适应的选项
图 9 再次展示了两种领域适应的选项,即上下文学习和微调。显而易见的下一个问题是,在特定情况下应该选择这两种方法中的哪一种。这一决策是一个权衡,可以从多个维度进行评估:
资源投资考虑因素和数据速度的影响:
数据可以被分类的一个维度是其中信息的速度。一方面,有慢速数据,其中包含的信息变化非常少。此类信息的例子有语言学概念、语言或写作风格、术语和来自行业或特定组织领域的缩写。另一方面,我们有快速数据,包含的信息相对频繁地更新。虽然实时数据是这一类数据最极端的例子,但来自数据库、企业应用程序或非结构化数据(如文档)的知识库的信息也是快速数据的常见变体。
图 10:数据速度
与动态提示相比,微调方法是一个更加资源密集的领域适应投资。从经济角度来看,这项投资需要仔细安排时机,因此微调主要应用于处理缓慢的数据。如果你希望处理实时或频繁变化的信息,动态提示方法更适合在相对较低的价格点获取最新信息。
任务模糊性及其他任务特定的考虑因素:
根据评估维度的不同,待执行的任务可能具有不同程度的模糊性。大型语言模型(LLMs)以自回归标记预测的方式进行推理,在每次迭代中,模型基于对模型词汇表中每个标记分配的概率进行采样来预测一个标记。这也是模型推理过程非确定性的原因(除非使用非常特定的推理配置),这可能导致相同提示下的不同回答。这种非确定性行为的影响取决于任务的模糊性以及特定评估维度的结合。
图 11:任务模糊性对不同模型评估目标的影响 — 来源:Anthropic Claude 3 Sonnet 通过 Amazon Bedrock
图 11 中所示的任务是一个“开放式问答”任务,旨在回答关于美国第一任总统乔治·华盛顿的问题。第一条回应来自 Claude 3,第二个答案则是在一个虚拟场景中由我创作的,场景假设将“1789”这个标记翻转成了“2017”。考虑到评估维度“事实正确性”受到标记翻转的重大影响,导致其表现低于预期。另一方面,对指令跟随或其他语言相关的度量(如困惑度评分)作为评估维度的影响较小,几乎没有影响,因为模型依然在按照指令进行,并且回答得连贯。语言相关的度量似乎比事实知识等其他度量更具模糊性。
这对我们的权衡意味着什么呢?让我们总结一下:虽然微调后的模型最终在更新后的参数知识基础上执行相同的任务,但动态提示从根本上改变了要解决的问题。以开放式问答任务为例,这意味着:一个在美国历史知识语料库上进行微调的模型很可能会在这个领域的问题上提供更准确的答案,因为它已经将这些信息编码到其参数知识中。然而,提示方法则从根本上通过添加上下文信息(无论是静态的还是动态的)将开放式问答问题转变为封闭式问答,从而降低了模型要解决任务的复杂性。
实证结果表明,在模糊性较低的情况下(例如需要领域特定的事实知识,并且无法容忍基于 AI 概率特性的幻觉),上下文学习技术更为适用。另一方面,微调非常适合需要将模型行为对准特定任务或任何与慢数据(如语言学或术语)相关的信息的情况,这些信息相比于事实知识,通常具有较高的模糊性,并且较少产生幻觉。
当重新考虑到 LLMs 本质上是通过语言空间来接近并解决问题时,这一观察结果变得相当明显。语言空间是一个高模糊性的领域。然后,通过将特定问题框架化为下一个标记预测的设置并最小化与 CLM 相关的损失,从而优化特定领域。
由于高模糊性任务与损失函数的相关性高于低模糊性任务(如事实正确性),因此表现优异的概率也较高。
若要获得关于这一维度的更多思考,您还可以查看 Heiko Hotz 的博客文章。
在这两个维度之上,最近的研究(例如Siriwardhana 等,2022)证明了这两种方法并不互相排斥,即在微调模型的同时,应用如提示工程和/或 RAG 等上下文学习技术,会比单独应用其中任何一种方法带来更好的结果。
接下来:
在这篇博客文章中,我们广泛介绍了领域适应,讨论了它在企业级生成 AI 业务应用中的紧迫性。通过上下文学习和微调,我们介绍了两种可供选择的不同选项以及在迈向领域适应模型或系统时需要做出的权衡。
接下来,我们将首先深入探讨动态提示技术。然后,我们将讨论不同的微调方法。
第一部分:领域适应简介——动机、选项、权衡 — 你现在就在这里!第二部分:深入探索上下文学习**第三部分:深入探索微调
深入探讨微调
脱离“舒适区”——大型语言模型领域适应方法深度解析系列的第三部分/共三部分
·发表于数据科学前沿 ·阅读时间 24 分钟·2024 年 6 月 3 日
--
图片来源:StableDiffusionXL(来自 Amazon Web Services)
想要将领域适应技术应用到大型语言模型(LLMs),以适应你的特定领域或使用案例吗?这篇三部分系列博客文章阐述了领域适应的动机,并深入探讨了实现这一目标的多种选项。此外,还提供了一个详细的指南,帮助你掌握整个领域适应过程,涵盖了流行的权衡选择。
第一部分:领域适应简介 — 动机、选项与权衡**第二部分:深入探讨上下文学习 第三部分:深入探讨微调 — 你正在阅读此部分!
注:所有图片,除非另有说明,均为作者提供。
回顾
在本系列博客文章的前一部分中,我们探讨了上下文学习的概念,作为一种强有力的方法来克服大规模语言模型(LLMs)“舒适区”限制。我们讨论了如何利用这些技术将任务转化并使其回到模型的专业领域,从而提升性能,并与有用性、诚实性和无害性等关键设计原则保持一致。在第三部分中,我们将重点转向第二种领域适应方法:微调。我们将深入探讨微调的细节,探索如何利用它来扩展模型的“舒适区”,从而通过将模型适应特定领域和任务来提升性能。我们还将讨论提示工程与微调之间的权衡,并根据数据流速、任务模糊性等因素提供选择合适方法的指导。
变换器基础
大多数最先进的 LLM 都采用变换器架构,这是一类深度神经网络架构,自从Vaswani 等人在 2017 年提出以来,已经颠覆了自然语言处理(NLP)领域,打破了该领域的所有常规基准。这个架构家族的核心区分点是一个叫做“注意力”(attention)的概念,它在根据上下文捕捉词语或更大语义单元的含义方面表现出色。
变换器架构由两个基本上不同的构建模块组成。一方面,“编码器”模块专注于将自然语言的语义转换为所谓的上下文化嵌入(contextualized embeddings),这些嵌入是向量空间中的数学表示。这使得编码器模型在使用这些向量表示进行下游确定性或概率性任务(如分类问题、命名实体识别或语义搜索)时特别有用。另一方面,解码器模块则以下一个标记预测为训练目标,因此在递归使用时能够生成文本。它们可以用于所有依赖文本生成的任务。这些构建模块可以独立使用,也可以结合使用。目前,生成式人工智能领域大多数提到的模型都是仅使用解码器的模型。这也是本文将重点讨论这一类型模型的原因。
图 1:变换器架构(改编自 Vaswani 等人,2017 年)
E2E 微调流程
微调利用迁移学习高效地将特定领域的专业知识注入到像 LLaMA2 这样的基础模型中。该过程包括通过在特定领域的数据上训练来更新模型的权重,同时保持整体网络架构不变。与需要大量数据集和计算资源的全预训练不同,微调在样本和计算方面都非常高效。从高层次来看,端到端的过程可以分为以下几个阶段:
图 2:端到端微调流程
- 数据收集与选择: 要输入模型的专有数据集需要经过仔细挑选。除此之外,针对特定微调目的,数据可能尚不可用,必须有目的地收集。根据可用数据和通过微调实现的任务,可能会选择具有不同定量或定性特征的数据(例如,标记数据、未标记数据、偏好数据——见下文)。除了数据质量方面,还需要考虑数据来源、保密性与知识产权、许可、版权、个人身份信息(PII)等维度。
LLM 预训练通常利用网络抓取和精心挑选的语料库,作为一种领域适应方法的微调特性意味着使用的数据集大多是针对特定组织、知识或任务领域的标记或未标记的精心挑选的语料库。
图 3:预训练与微调:数据构成与选择标准
虽然这些数据可以通过不同方式获取(文档库、人类创建的内容等),但这强调了对于微调而言,必须谨慎地根据质量选择数据,但如前所述,还要考虑保密性和知识产权、许可、版权、个人身份信息(PII)等问题。
图 4:每种微调方法的数据需求
此外,一个重要的维度是将训练数据集分类为未标记数据和标记数据(包括偏好数据)。领域适应微调需要未标记的文本数据(与其他微调方法不同,见图 4)。换句话说,我们可以简单地使用任何我们认为相关且足够质量的自然语言全文档。这可以是用户手册、内部文档,甚至是法律合同,具体取决于实际的应用场景。
另一方面,像指令-上下文-响应数据集这样的标记数据集可以用于监督微调方法。最近,通过强化学习方法将模型与实际用户反馈对齐,取得了显著成果,利用了人类或机器创建的偏好数据,例如二元人类反馈(好/差)或多项响应排序。
与未标注数据不同,标注数据集的收集更为困难和昂贵,特别是在大规模收集且具备足够领域专业知识时。像HuggingFace Datasets这样的开源数据中心是标注数据集的良好来源,尤其是在那些相关人群广泛达成共识的领域(例如,红队测试的毒性数据集),并且使用开源数据集作为模型真实用户偏好的代理已足够。
尽管如此,许多使用场景更为具体,开源的代理数据集不足以满足需求。这时就需要由真正的人类标注的数据集,尤其是那些具有显著领域专业知识的人类标注。像Amazon SageMaker Ground Truth这样的工具可以帮助收集数据,无论是通过提供完全托管的用户界面和工作流,还是提供完整的劳动力。
最近,合成数据收集已成为微调领域越来越多讨论的话题。这是使用强大的大型语言模型(LLMs)合成创建标注数据集的实践,无论是用于监督微调(SFT)还是偏好对齐。尽管这种方法已经展示了有希望的结果,但目前仍处于进一步研究阶段,必须证明它在实际中能够在大规模上发挥作用。
-
数据预处理: 选定的数据需要进行预处理,以使其对下游训练算法“易于消化”。常见的预处理步骤包括:
-
质量相关的预处理, 如格式化、去重、PII(个人身份信息)过滤。
-
与微调方法相关的预处理: 如将数据呈现为用于监督微调的提示模板。
-
与 NLP 相关的预处理, 如分词、嵌入、分块(根据上下文窗口)。
-
模型训练: 根据选定的微调方法训练深度神经网络。我们将在下面进一步详细讨论的几种流行微调方法包括:
-
持续预训练即领域适应微调: 在完整文本数据上进行训练,对齐任务与下一个令牌预测任务相关。
-
监督微调: 利用标注数据的微调方法,对齐与目标标签相关。
-
偏好对齐方法: 利用偏好数据的微调方法,依据模型/系统的实际用户定义的期望行为进行对齐。
随后,我们将深入探讨每个阶段,首先介绍训练方法和不同的微调方法,然后再讨论数据集和数据处理要求。
训练
本节将探讨解码器变换器模型的训练方法。该方法适用于预训练和微调。
与传统的机器学习训练方法(如使用未标注数据的无监督学习或使用标注数据的有监督学习)不同,变压器模型的训练采用了一种称为自监督学习的混合方法。这是因为,尽管输入的是未标注的文本数据,算法实际上通过掩盖特定的输入词元,自我监督地进行学习。以以下输入序列“Berlin is the capital of Germany.”为例,它自然地变成了一个监督样本,其中 y 是被掩盖的词元,而 X 是其余部分。
图 5:语言模型的自监督训练
上述自监督训练方法通过优化模型权重,朝着特定的语言模型(LM)损失函数进行调整。而在编码器模型的训练中,利用掩码语言建模(MLM)通过随机掩盖词元来利用双向上下文,而仅解码器模型则依赖因果语言建模(CLM)方法,通过始终掩盖序列中的最右侧词元来使用单向上下文。简而言之,这意味着它们是以自回归方式,基于前面的词元作为语义上下文来预测下一个词元。除此之外,还有其他语言模型方法,例如排列语言建模(PLM),其通过条件化模型将一系列随机打乱的词元重新排列成正确顺序。
图 6:语言建模变体及损失函数
通过使用 CLM 任务作为代理,创建了预测值和真实标签,这些可以用来计算预测损失。因此,模型词汇表中所有词元的预测概率分布将与真实标签进行比较,真实标签是一个稀疏向量,其中表示真实标签的词元的概率为 1.0。实际使用的损失函数取决于具体的模型架构,但像交叉熵损失或困惑度损失这样的损失函数,在像词元预测这样的分类问题中表现良好,因此被广泛使用。损失函数被用来逐步最小化损失,从而通过在深度神经网络反向传播中执行梯度下降来优化模型权重,朝着我们的训练目标进行每次迭代。
微调变体 — 场景
够了,理论部分讲解完毕,我们进入实践部分。假设你是来自生物技术领域的一个组织,旨在利用 LLM,比如 LLaMA2,作为基础模型,处理关于 COVID-19 疫苗研究的各种自然语言处理任务。不幸的是,在许多维度上,这个领域并不在通用现成预训练 LLM 的“舒适区”内,导致其性能低于你的预期标准。在接下来的章节中,我们将讨论不同的微调方法,以及它们如何帮助提升 LLaMA2 在我们假设的场景中在多个维度上的表现。
微调变体——持续预训练,也称为领域适应微调
正如标题所示,尽管该领域开始趋同于“持续预训练”这一术语,但关于本节讨论的微调方法,社区尚未达成一致的明确术语。那么,这种微调方法究竟是关于什么的呢?
生物技术领域的研究论文在写作风格上颇为独特,充满了领域特定的知识以及行业或甚至组织特有的缩写(例如,Polack et al, 2020; 见图 7)。
图 7:通过 Polack 等人(2020 年)的例子说明研究论文中的领域特性
另一方面,详细分析 Meta LLaMA 模型(Touvron 等,2023 年;图 8)和 TII Falcon 模型家族(Almazrouei 等,2023 年;图 9)的预训练数据集混合物表明,通用大型语言模型(LLM)中仅有 2.5%和 2%的数据来自研究领域甚至生物技术领域(LLaMA 3 家族的预训练数据混合物在博客发布时尚未公开)。
图 8:Meta LLaMA 模型的预训练数据集混合物——来源:Touvron 等(2023 年)
图 9:TII Falcon 模型的预训练数据集混合物——来源:Almazrouei 等(2023 年)
因此,我们需要通过利用微调来弥合这一差距,从而扩大模型的“舒适区”,以便在特定任务中取得更好的表现。持续预训练正是擅长上述提到的维度。它包括在特定数据集上调整预训练 LLM 的过程,该数据集由纯文本数据组成。这种技术有助于将领域特定的信息(如语言模式、领域特定语言、缩写等)或隐含在原始全文中的信息注入到模型的参数知识中,使模型的回答更符合这一特定语言或知识领域。对于这种方法,预训练的解码器模型通过无标注文本数据进行下一个词预测的微调。这使得持续预训练成为与预训练最相似的微调方法。
在我们的示例中,我们可以将提到的论文内容与相关领域的文献结合,并将其转换为一个连接的文本文件。根据调整目标和其他需求,可以应用数据整理步骤,如去除不必要的内容(例如,作者、目录等)、去重或减少个人身份信息(PII)。最后,数据集会进行一些特定于自然语言处理(NLP)的预处理(例如,分词、根据上下文窗口进行切分等——见上文),然后用于训练模型。训练本身是基于经典的 CLM 训练,如前一节所讨论的那样。在对 LLaMA2 进行了继续预训练,使用一组来自生物技术(BioTech)领域的研究出版物之后,我们现在可以将其用于这个特定领域,作为文本生成模型“BioLLaMA2”。
微调变体——监督微调(SFT)
不幸的是,我们人类并不喜欢将我们希望解决的问题以纯粹的文本补全/标记预测的形式来框定。相反,我们是一个会话性物种,尤其是在我们旨在完成任务时,更倾向于表现出聊天或指令性行为。
因此,我们需要模型行为中超越简单下一个标记预测的复杂性。这就是监督微调方法发挥作用的地方。监督微调(SFT)是指将一个预训练的语言模型(LLM)对准特定数据集,并使用带标签的示例进行训练的过程。这种技术对于定制模型的响应,以适应特定领域或任务至关重要,例如上述提到的对话性或遵循指令的行为。通过在一个密切代表目标应用的数据集上进行训练,SFT 使得 LLM 能够发展更深的理解,并在符合专门要求和行为的情况下产生更准确的输出。
除了上述提到的应用,SFT 的良好示例还包括将模型训练用于问答、数据抽取任务(例如实体识别)或红队测试(以防止有害响应)。
图 10:E2E 监督微调流程
如上所述,SFT 需要一个带标签的数据集。虽然开源中有许多通用的带标签数据集,但为了将模型最优化以适应你的特定用例、行业或知识领域,手动制作一个定制的数据集可能更有意义。最近,使用像 Claude 3 或 GPT-4 这样的强大 LLM 来制作此类数据集,已经成为一种资源和时间高效的替代人工标注的方法。
“dolly-15k”数据集是一个流行的通用开放源代码指令微调数据集,由 Databricks 的员工手动创建。它包含大约 15k 条指令和上下文示例,并附有期望的响应。该数据集可以用于将我们的 BioLLaMA2 模型对齐,以便遵循指令,例如用于封闭式问答任务。对于面向指令跟随的 SFT,我们将继续将数据集中的每一项转化为完整的文本提示,并嵌入到一个表示我们希望对齐模型的任务的提示结构中。其结构可能如下所示:
### Instruction:
{item.instruction}
### Context:
{item.context}
### Response:
{item.response}
提示模板可以根据模型家族有所不同,因为某些模型更偏好 HTML 标签或其他特殊字符而非井号(#)。在所有数据项被合并成一大块文本之前,该程序将应用于数据集中的每个条目。最后,在上述 NLP 特定预处理之后,该文件可以通过利用下一个令牌预测和基于 CLM 的训练目标来训练模型。由于模型持续暴露于这一特定提示结构,它将学会坚持这一结构并以相应的方式行动——在我们的案例中,就是指令跟随。在将我们的 BioLLaMA2 与 dolly-15k 数据集对齐之后,我们的 BioLLaMA2-instruct 模型将严格按照通过提示提交的指令执行。
微调变体 — 人类偏好对齐技术(RLHF/PPO, DPO, KTO, ORPO)
使用 BioLLaMA2,我们拥有一款适应生物技术研究领域的模型,能够方便地根据我们的指令满足用户的预期。但等等——这个模型真的与我们的实际用户对齐吗?这突显了迄今为止讨论的微调方法的一个核心问题。我们所使用的数据集只是我们认为用户喜欢或需要的内容的代理:包括所选研究论文中的内容、语言、缩略语,以及少数 Databricks 员工在制作 dolly-15k 时设定的期望行为。这与以用户为中心的产品开发概念相对立,而后者是敏捷产品开发的核心原则之一。通过反复循环地融入实际目标用户的反馈,在开发优秀产品时被证明非常成功。事实上,如果我们旨在为用户构建一个优秀的体验,这正是我们想要做的!
图 11:强化学习框架
考虑到这一点,研究人员在将人类反馈纳入到大语言模型性能提升中的方法上投入了大量精力。在这条道路上,他们意识到与(深度)强化学习(RL)有显著的重叠,强化学习涉及的是在环境中执行动作的自主代理,这些动作会产生下一个状态,并总是与奖励相关联。这些代理基于策略或价值图进行行动,而这一策略在训练阶段逐步优化,以最大化奖励。
图 12:适应性强化学习框架用于语言建模
这一概念——在大语言模型(LLMs)的世界中——归结为大语言模型本身充当代理。在推理过程中,凭借其自回归的 token 预测特性,每一步都会执行一个动作,其中动作空间是模型的词汇表,环境则是所有可能的 token 组合。每次新的推理周期开始时,都会建立一个新的状态,并根据人类反馈给予奖励,理想情况下这个奖励与人类反馈相关。
基于这一思想,已经提出并测试了几种人类偏好对齐方法。接下来,我们将逐一介绍其中一些最重要的方法:
来自人类反馈的强化学习(RLHF)与近端策略优化(PPO)
图 13:RLHF 的奖励模型训练
来自人类反馈的强化学习是早期生成式 AI 热潮的主要技术支撑之一,推动了像 Anthropic Claude 或 GPT-3.5 等大型解码器模型的突破,并进一步促进了用户对齐的方向发展。
RLHF 以两步过程工作,具体见图 13 和图 14:
第一步(图 13):首先,需要训练一个奖励模型,以便在实际的基于强化学习(RL)的训练方法中使用。因此,需要提供一个与目标对齐的提示数据集(以我们的 BioLLaMA2-instruct 模型为例,这将是由指令和上下文组成的配对)供模型优化,并要求不仅生成一个推理结果,而是两个或更多的推理结果。这些结果将提交给人工标注员进行评分(第一、第二、第三等),根据优化目标进行排序。此外,还有一些开源的偏好排序数据集,其中“Anthropic/hh-rlhf”是专门为红队测试以及诚实和无害性目标量身定制的。在经过标准化步骤并将其转化为奖励值之后,基于单一的样本-奖励对训练奖励模型,其中样本是单个模型响应。奖励模型的架构通常与待微调的模型类似,通过添加一个小的头部,将潜在空间投影到奖励值上,而不是令牌的概率分布。然而,这个模型的理想参数大小仍然是一个研究课题,过去模型提供者采取了不同的方法。
图 14:基于 PPO 的强化学习模型调优用于 RLHF
第二步(图 14):我们的新奖励模型现在用于训练实际模型。因此,另一组提示被输入待调整的模型(图中的灰色框),每次生成一个响应。随后,这些响应被输入奖励模型以获取各自的奖励值。接着,使用基于策略的强化学习算法——近端策略优化(PPO)来逐步调整模型的权重,以最大化分配给模型回答的奖励。与 CLM 不同,这种方法不是使用梯度下降,而是利用梯度上升(或者对1 - 奖励使用梯度下降),因为我们现在试图最大化一个目标(奖励)。为了增强算法的稳定性,防止在训练过程中由于像 PPO 这样的基于强化学习的方法引起模型行为的剧烈漂移,我们在奖励项中加入了预测偏移惩罚,惩罚那些在相同输入提示上偏离初始语言模型预测概率分布过多的回答。
除了使用 PPO 进行的 RLHF(目前是最广泛采用且已证明有效的偏好对齐方法),还开发了其他几种方法。在接下来的几个部分中,我们将深入探讨这些方法中的一些高级内容。这部分仅供高级读者阅读,因此根据你在深度学习和强化学习方面的经验水平,你可能想跳过直接阅读下一部分“决策流程图——选择哪个模型,选择哪条微调路径”。
直接策略优化(DPO)
直接策略优化(DPO)是一种从 RLHF 中推导出来的偏好对齐方法,解决了 RLHF 的两个主要缺点:
-
首先训练奖励模型需要额外的资源投入,并且根据奖励模型的大小,可能会是一个较大的开销。
-
使用 PPO 的 RLHF 训练阶段需要大量的计算集群,因为三个模型副本(初始语言模型、调整后的语言模型、奖励模型)需要在低延迟的设置中同时托管和协调。
-
RLHF 可能是一个不稳定的过程(→预测偏移惩罚尝试缓解这一问题)
图 15:RLHF 与 DPO 对比(Rafailov 等,2023 年)
DPO 是一种替代的偏好对齐方法,由 Rafailov 等人于 2023 年提出。DPO 的核心思想是跳过奖励模型的训练,直接在偏好数据上调整最终的偏好对齐语言模型。通过应用一些数学调整,将奖励模型的参数化(奖励项)转化为损失函数(图 16),同时用偏好数据上的概率值替代实际的奖励值,从而实现这一目标。
图 16:DPO 的损失函数(Rafailov 等,2023 年)
这节省了朝着偏好对齐模型前进过程中计算和算法的复杂性。尽管论文也显示了与 RLHF 相比的性能提升,但这种方法相对较新,因此其结果仍需经过实际验证。
卡尼曼-特沃斯基优化(KTO)
现有的将语言模型与人类反馈对齐的方法,如 RLHF 和 DPO,需要偏好数据——即一对对输出,其中一个在给定输入下被认为优于另一个。然而,在现实世界中,大规模收集高质量的偏好数据是具有挑战性且昂贵的。偏好数据常常受到噪声、不一致性和不传递性问题的困扰,因为不同的人类评估者可能对哪个输出更好存在冲突的看法。KTO 由 Ethayarajh 等人(2024 年)提出,作为一种可以使用更简单、更丰富的信号的替代方法——即仅仅知道给定输出对于输入是否是可取的,而无需知道输出之间的相对偏好。
图 17:根据卡尼曼和特沃斯基的前景理论推导的决策的隐含人类效用(Ethayarajh 等,2024 年)
从高层次来看,KTO 的工作原理是定义一个奖励函数,用于捕捉生成结果的相对“优越性”,然后优化模型,以最大化在卡尼曼-特沃斯基价值函数下该奖励的期望值。卡尼曼和特沃斯基的前景理论解释了人类如何以一种有偏但明确的方式对不确定的结果做出决策。该理论认为,人类效用依赖于一个在收益上是凹的、在损失上是凸的价值函数,并且有一个分离收益与损失的参考点(见图 17)。KTO 直接优化这一人类效用的概念,而不仅仅是最大化偏好的可能性。
图 18:RLHF 与 DPO 与 KTO(Ethayarajh 等,2024)
关键创新在于 KTO 只需要一个二元信号来表示输出是期望的还是不期望的,而不是完整的偏好对。这使得 KTO 在数据效率上优于基于偏好的方法,因为二元反馈信号更加丰富且更容易收集(见图 18)。
KTO 特别适用于偏好数据稀缺或收集成本高昂的场景,但你可以获得更多关于模型输出质量的二元反馈信号。根据论文,KTO 的表现可以与基于偏好的方法(如 DPO)匹敌,甚至在较大规模的模型下超过它们。然而,这还需要在实践中进行大规模验证。当目标是直接优化人类效用而不仅仅是偏好可能性时,KTO 可能是更好的选择。然而,如果偏好数据非常高质量,噪声或不传递性很少,那么基于偏好的方法可能仍然是更好的选择。KTO 在处理极端数据不平衡和避免某些情况下需要监督微调方面也具有理论优势。
比值比偏好优化(ORPO)
ORPO 的关键动机是解决现有偏好对齐方法(如 RLHF 和 DPO)的局限性,这些方法通常需要单独的监督微调(SFT)阶段、参考模型或奖励模型。Hong 等(2024)的论文认为,单独使用 SFT 可能会无意中增加生成不期望风格的令牌的可能性,因为交叉熵损失并没有为不受欢迎的响应提供直接的惩罚。同时,他们认为 SFT 对于收敛到强大的偏好对齐模型至关重要。这导致了一个资源密集型的两阶段对齐过程。通过将这些阶段合并为一个,ORPO 旨在保留 SFT 的领域适应优势,同时识别并缓解偏好对齐方法所期望的、不希望的生成风格(见图 19)。
图 19:RLHF 与 DPO 与 ORPO(Hong 等,2024)
ORPO 引入了一种新颖的偏好对齐算法,通过引入基于赔率比的惩罚到传统的因果语言建模绑定损失(例如交叉熵损失)中。ORPO 的目标函数由两个部分组成:SFT 损失和相对比率损失(LOR)。LOR 项通过最大化生成偏好响应和不偏好响应的可能性之间的赔率比,有效地惩罚模型对被拒绝的响应分配高概率。
图 20:ORPO 损失函数将 SFT 损失和偏好赔率比结合到一个单一的损失项中
ORPO 在你想要微调一个预训练语言模型以适应特定领域或任务时尤其有用,同时确保模型的输出与人类偏好一致。它可以应用于你拥有成对偏好数据集的场景(yw = 喜好,yl = 不喜好,例如 UltraFeedback 或 HH-RLHF 数据集)。考虑到这一点,ORPO 被设计成比 RLHF 和 DPO 更高效、有效的替代方案,因为它不需要单独的参考模型、奖励模型或两步微调方法。
决策流程图——选择哪种模型,选择哪条微调路径
在深入研究了大量微调方法之后,显然的问题是应该选择哪个模型开始,以及根据特定要求选择哪种方法。选择合适的微调模型的方法是一个两步过程。第一步与选择一个没有微调意图的基础模型非常相似,包括在以下维度(不完全列举)下的考虑因素:
-
使用的平台:每个平台都提供一组可以访问的模型。需要考虑到这一点。请注意,模型的可用性可能存在区域性的差异。请查阅相关平台的文档以获取更多信息。
-
性能:组织应致力于为特定任务使用最简洁的模型。虽然无法给出通用的指导,并且微调可以显著提升模型性能(较小的微调模型可以超越较大的通用模型),但利用基础模型的评估结果作为指标会有所帮助。
-
预算(TCO):通常,较大的模型需要更多的计算资源,并可能需要多 GPU 实例来进行训练和服务,跨多个加速器进行处理。这直接影响到训练和推理成本、训练和推理的复杂性、所需资源和技能等因素,作为模型整个生命周期中总拥有成本(TCO)的一部分。需要与分配的短期和长期预算保持一致。
-
许可模型: 模型,无论是专有的还是开源的,都有许可限制,具体取决于使用领域和商业模式。这一点需要考虑。
-
治理、伦理、负责任的 AI: 每个组织都有与这些维度相关的合规指南。这需要在选择模型时予以考虑。
示例:一个组织可能决定考虑 LLaMA 2 模型,并基于基础模型的评估结果排除使用像 Anthropic Claude 或 AI21Labs Jurassic 这样的专有模型。此外,他们决定只使用 7B 参数版本的模型,以便能够在单 GPU 实例上进行训练和服务。
第二步是将初步选择的模型缩小到 1 到几个模型,以供实验阶段使用。最终选择哪种具体方法取决于期望进入语言模型微调生命周期的起始点,具体见下图。
图 21:通过微调进行领域适应的决策流程图
因此,以下维度需要考虑:
-
要执行的任务: 不同的使用案例要求特定的模型行为。对于某些使用案例,简单的文本补全模型(下一个词预测)可能就足够了,但大多数使用案例需要特定的任务行为,如健谈性、遵循指令或其他任务特定的行为。为了满足这一需求,我们可以从期望的任务开始,采用逆向工作的方法。这意味着我们需要定义特定的微调路径,最终使模型与特定任务对齐。就示意图而言,这意味着模型必须——与期望的模型行为对齐——最终进入蓝色、橙色或绿色圆圈,同时微调路径沿着流程图的可能路径定义。
-
选择合适的起点(只要合理): 虽然我们应该非常明确微调过程的终点,但我们可以通过选择相应的基础模型,在流程图中的任何位置开始。不过,这一选择必须是合理的——在模型库中拥有数百万个已发布模型的时代,检查是否有人已经执行过微调步骤并分享了结果模型是有意义的,特别是在考虑流行模型与开源数据集的结合时。
-
微调是一个迭代的、潜在的递归过程: 在实现我们期望的模型的过程中,可以进行多次后续的微调工作。然而,请注意,灾难性遗忘是我们需要关注的问题,因为模型无法在其权重中编码无限量的信息。为缓解这一问题,可以利用如 LoRA 等参数高效的微调方法,正如这篇论文和博客所示。
-
特定任务性能提升目标: 微调是为了提升模型在特定任务中的表现。如果我们希望在语言模式(领域特定语言、缩略语等)或训练数据中隐含的信息上提升表现,持续预训练是正确的选择。如果我们希望在特定任务上提升性能,则应选择监督微调。如果我们希望将模型行为与实际用户对齐,则应选择人类偏好对齐。
-
数据可用性: 训练数据也会影响我们选择的路径。通常,组织持有大量未标注的文本数据,而非标注数据,而获取标注数据可能是一个昂贵的任务。在穿越流程图时,这一维度需要被考虑。
使用这种从后向前的方法,并结合上述流程图,我们可以确定开始的模型以及在穿越微调流程图时需要采取的路径。
为了使这一点更加明显,我们提供了两个示例:
图 22:示例 1 的决策流程图
示例 1:根据上述微调部分中举的例子,我们可以构建一个用于我们特定用例的指令模型,符合我们实际用户的偏好。然而,我们希望在生物技术领域提升性能。未标注的数据以研究论文的形式可用。我们选择 LLaMA-2–7b 模型家族作为期望的起点。由于 Meta 没有发布 LLaMA-2–7b 指令模型,我们从文本补全模型 LLaMA-2–7b-base 开始。然后,我们对研究论文语料库进行持续的预训练,接着在像 dolly-15k 这样的开源指令数据集上进行监督微调。这将得到一个经过指令微调的生物技术版 LLaMA-2–7B-base,我们称之为 BioLLaMA-2–7b-instruct。在下一步中,我们希望将模型与实际用户的偏好对齐。我们收集偏好数据集,训练奖励模型,并使用带有 PPO 的 RLHF 对我们的模型进行偏好对齐。
图 23:示例 2 的决策流程图
示例 2:在这个例子中,我们旨在使用一个聊天模型,但需要使其与实际用户的偏好对齐。我们选择 LLaMA-2–7b 模型家族作为理想的起点。我们发现 Meta 提供了一个现成的聊天微调模型 LLaMA-2–7b-chat,我们可以作为起点使用。在下一步中,我们希望将模型与我们实际用户的偏好对齐。我们收集用户的偏好数据集,训练一个奖励模型,并使用 RLHF 和 PPO 进行偏好对齐。
结论
生成性人工智能在企业和组织中有许多令人兴奋的应用场景。然而,这些应用通常比个人消费者用途(如生成食谱或演讲)要复杂得多。对于公司来说,AI 需要理解组织的特定领域知识、流程和数据。它必须与现有的企业系统和应用程序集成,并为不同的员工和角色提供高度定制化的体验,同时以无害的方式进行操作。为了在企业环境中成功实施生成性 AI,技术必须经过精心设计,并根据组织的独特需求量身定制。仅仅使用一个通用的、公开训练的模型是不够的。
在这篇博客文章中,我们讨论了域适应如何通过克服模型面临超出其“舒适区”任务的情况来帮助弥合这一差距。通过上下文学习和微调,我们深入探讨了两种强大的域适应方法。最后,我们讨论了在决定这两种方法时需要权衡的因素。
成功弥合强大 AI 能力与现实世界商业需求之间的差距是释放生成性 AI 在公司中全部潜力的关键。
深入探讨上下文学习
跳出“舒适区” — 深入探索 LLM 领域适应方法的第二部分/共三部分
·发表于Towards Data Science ·阅读时间:10 分钟·2024 年 5 月 31 日
--
图片由 StableDiffusionXL 提供,托管于 Amazon Web Services
探索将大语言模型(LLMs)适应特定领域或用例?这篇三部分博客系列解释了领域适应的动机,并深入探讨了实现这一目标的各种选项。此外,还提供了一份详细的指南,帮助掌握整个领域适应过程,并涵盖了常见的权衡取舍。
第一部分:领域适应简介 — 动机、选项、权衡 第二部分:深入探讨上下文学习 — 你现在就在这里!第三部分:深入探讨微调
注意:除非另有说明,所有图片均为作者提供。
回顾
在本博客系列的第一部分,我们讨论了生成性人工智能的快速发展,以及像 Claude、GPT-4、Meta LLaMA 和 Stable Diffusion 这样的语言模型的出现。这些模型在内容创作中展示了出色的能力,引发了对潜在风险的热情与担忧。我们强调,虽然这些人工智能模型非常强大,但它们也有固有的局限性和“舒适区”——即它们擅长的领域和当它们被推向超出其专业领域时,表现可能下降的领域。这可能导致模型的响应质量低于预期,进而产生幻觉、偏见输出或其他不良行为。
为了应对这些挑战,并使企业能够战略性地使用生成性人工智能,我们提出了三个关键设计原则:有益性、诚实性和无害性。我们还讨论了如何通过领域适配技术,如上下文学习和微调,克服这些模型的“舒适区”局限性,创建符合企业标准的生成性人工智能应用程序。在第二部分中,我们将深入探索上下文学习的世界,研究如何利用这些技术转变任务,并将其带回模型的舒适区。
上下文学习的期望结果是什么?
上下文学习旨在利用外部工具修改要解决的任务,以一种使任务回到(或更接近)模型舒适区的方式。在大型语言模型(LLM)的世界中,这可以通过提示工程来实现,提示工程涉及通过模型提示注入源知识,从而改变任务的整体复杂度。它可以以一种相对静态的方式执行(例如少量提示),但更复杂的动态提示工程技术,如检索增强生成(RAG)或代理,已被证明具有强大的能力。
图 1:利用上下文学习克服幻觉——来源:Claude 3 Sonnet via Amazon Bedrock
在本博客系列的第一部分,我们注意到,通过图 1 中展示的例子,添加一个静态上下文(如演讲者简介)可以帮助减少任务复杂度,使得模型更容易解决,从而获得更好的模型结果。接下来,我们将深入探讨上下文学习的更高级概念。
从静态到动态的上下文注入
“智慧的衡量标准是改变的能力。”(阿尔伯特·爱因斯坦)
虽然上述静态上下文注入的示例对于静态用例效果良好,但它缺乏在不同和复杂领域之间扩展的能力。假设我们封闭问答任务的范围不仅仅局限于我个人,而是扩展到一个大型会议的所有发言人,因此涉及到数百个发言人简历。在这种情况下,手动识别和插入相关的上下文片段(即发言人简历)变得繁琐、容易出错且不实际。从理论上讲,最近的模型支持高达 200k 个 token 或更多的巨大上下文大小,不仅能容纳这些数百个发言人简历,还能容纳整个书籍和知识库。然而,这种方法并不理想,原因有很多,比如按 token 计费的成本、计算需求、延迟等。
幸运的是,针对动态方法中最适合吸收的上下文片段的优化内容检索方法有很多——其中一些是确定性的(例如在结构化数据上进行 SQL 查询),其他则依赖于概率系统(例如语义搜索)。将这两种组件结合在一起,形成一个集成的封闭问答方法,带有动态上下文检索和注入,已经证明极其强大。通过这种方式,可以连接来自各种数据源的大量(甚至是无限的?)数据——从关系数据库或图数据库到向量存储,再到企业系统或实时 API 等。为实现这一目标,识别出的最相关的上下文片段将被提取,并动态地注入到用于生成解码器模型的提示模板中,以完成所需任务。图 2 展示了这一过程,举例说明了一个面向用户的问答应用(例如聊天机器人)。
图 2:与各种数据源的动态上下文注入
检索增强生成(RAG)
目前最流行的动态提示工程方法是 RAG(检索增强生成)。当试图动态地吸收来自大型全文知识库的上下文时,这种方法表现得很好。它通过将语义搜索检索到的动态上下文增强开放问答任务,从而将开放问答任务转变为封闭问答任务,结合了两种概率方法。
图 3:AWS 上的检索增强生成(RAG)
首先,文档被切分成易于处理的块。然后,使用编码器 LLM 来创建这些片段的上下文化嵌入,将每个片段的语义以向量的形式编码到数学空间中。该信息存储在向量数据库中,作为我们的知识库。这样,向量作为主键使用,而文本本身及其可选元数据将一同存储。
(0) 如果是用户提问,提交的输入将通过相同的嵌入模型进行清洗和编码,创建用户问题在知识库向量空间中的语义表示。
(1) 该嵌入随后用于在整个知识库中基于向量距离度量进行相似性搜索——假设与用户问题在向量空间中具有最高相似度的 k 个片段可能最适合用来为问题提供上下文支持。
(2) 在下一步,这些最相关的 k 个片段将与用户的初始问题一起作为上下文传递给解码器生成的 LLM,从而形成一个封闭的问答任务。
(3) LLM 根据应用系统提示(例如,聊天机器人风格)中的指导,以有根据的方式回答问题。
知识图谱增强生成(KGAG)
知识图谱增强生成(KGAG)是另一种动态提示方法,它将结构化的知识图谱与任务进行结合,从而增强语言模型输出的事实准确性和信息丰富性。集成知识图谱可以通过多种方法实现。
图 4:知识图谱增强生成(KGAG)——来源:Kang 等人(2023)
作为其中之一,Kang 等人 (2023) 提出的 KGAG 框架由三个关键组成部分构成:
(1) 与上下文相关的子图检索器根据当前对话历史 x 从整体知识图谱 G 中检索相关子图 Z。为此,模型为知识图谱中的每个三元组 z = (eh, r, et) 定义一个检索得分,该得分通过对话历史 x 和候选三元组 z 的嵌入进行内积计算。三元组嵌入是通过图神经网络(GNNs)生成的,用于捕捉知识图谱的关系结构。然后,检索分布 p(Z|x) 被计算为各个三元组检索得分 p(z|x) 的乘积,从而使得模型能够仅检索出与给定对话上下文最相关的子图 Z。
(2) 模型需要将检索到的子图 Z 与文本序列 x 一起编码,以供语言模型使用。一种简单的方法是将 Z 中实体和关系的标记直接加到输入 x 的前面,但这种做法违反了像置换不变性和关系反转不变性等重要属性。为了解决这个问题,论文提出了一种“不可变且高效”的图编码方法。该方法首先对 Z 中的唯一实体进行排序并编码,然后基于图结构应用学习的仿射变换来扰动实体嵌入。这种方法在满足所需的不变性属性的同时,还比将所有三元组标记加到前面的做法更具计算效率。
(3) 模型使用对比学习目标,确保生成的文本与检索到的子图 Z 一致。具体来说,它通过最大化检索子图和生成文本表示之间的相似度,同时最小化与负样本之间的相似度,来鼓励模型生成真实反映检索子图中事实知识的回应。
通过结合这三种组件——子图检索、不变图编码和图-文本对比学习——KGAG 框架能够生成既流畅又事实准确的基于知识的回应。
KGAG 特别适用于对话系统、问答系统以及其他需要生成信息丰富且事实准确回应的应用。它可以应用于能够访问相关知识图的领域,如百科知识、产品信息或领域特定事实。通过结合语言模型和结构化知识的优势,KGAG 能够生成既自然又值得信赖的回应,使其成为构建智能对话代理和知识密集型应用的宝贵工具。
思维链(CoT)——顺序分解问题
思维链(CoT)是一种由Wei 等人于 2023 年提出的提示工程方法。通过向模型提供指令或少量结构化推理步骤的示例,帮助解决问题,这大大降低了问题的复杂度,使得模型能够更有效地求解。
图 5:思维链提示(CoT)——来源:Wei 等人(2023)
CoT 提示的核心思想是模仿人类在解决复杂的多步骤推理任务时的思维过程。就像人类将复杂问题分解为中间步骤,并在解决每个步骤后依次得到最终答案一样,CoT 提示鼓励语言模型生成连贯的思维链——一系列中间推理步骤,最终得出解决方案。图 5 展示了一个例子,其中模型通过生成思维链来解决一个本来会出错的数学应用题。
论文强调了 CoT 提示的几个吸引人的特性。首先,它允许模型将多步骤问题分解为可管理的中间步骤,并将更多的计算分配给需要更多推理步骤的问题。其次,思维链为模型的推理过程提供了一个可解释的窗口,有助于调试并理解推理路径可能出现偏差的地方。第三,CoT 推理可以应用于各种任务,如数学题、常识推理和符号操作,这使其有可能应用于任何通过语言可解的任务。最后,足够大的现成语言模型只需通过在少量示例中加入此类推理序列的例子,即可轻松生成思维链。
推理与行动(ReAct)——实现智能体能力
ReAct 提示是 Yao 等人(2023) 引入的另一种新颖技术,它通过使语言模型能够无缝地协同推理与行动,进一步推动了通用任务求解的进展。其核心思想是扩大模型的行动空间,不仅包括特定领域的行动,还包括自由形式的语言“思维”,使模型能够推理任务、制定计划、跟踪进展、处理异常并结合外部信息。
在 ReAct 中,语言模型通过少量示例的人类轨迹进行提示,这些轨迹可以根据思维/推理步骤触发在环境中采取的行动。对于以推理为主要任务的任务,思维和行动交替进行,允许模型在行动之前进行推理。对于更开放的决策任务,思维可以根据需要稀疏且异步地出现,以制定高级计划、根据观察进行调整或查询外部知识。
ReAct 将大语言模型在多步骤推理(如递归思维链提示)方面的优势与它们在环境中行动和互动的能力相结合。通过将推理扎根于外部上下文并允许信息在推理与行动之间双向流动,ReAct 克服了以往将推理和行动孤立处理的工作中的关键局限性。
论文证明,ReAct 在问答、事实验证、文本游戏和网页导航任务中实现了强大的少样本性能。与仅依赖模型内部知识的思维链提示不同,ReAct 允许模型通过行动将外部来源的最新信息融入其推理过程中。行动执行动态上下文检索,整合如 RAG、KGAG,甚至网页搜索或 API 调用等数据源。这使得推理过程更加稳健,且不容易产生幻觉。相反,将推理注入仅有行动的方式,可以实现更智能的长期规划、进度跟踪和灵活调整策略——超越了简单的行动预测。
图 6:推理与行动(ReAct)提示——来源:Google
图 6(由 Google 插图)展示了不同的提示工程技术示例(包括少量示例和指令的系统提示被隐藏),这些技术尝试解决源自 HotpotQA 数据集的问答问题(Yang 等,2018)。与其他选项相比,ReAct 通过将推理与行动以递归方式结合,展示了该任务的强大性能。
接下来:
在这篇博文中,我们探讨了上下文学习作为一种强大的领域适应方法。在理解其基本机制后,我们讨论了常用的静态和动态提示工程技术及其应用。
在本系列博文的第三部分,我们将通过微调讨论不同的微调方法。
第一部分:领域适应介绍 — 动机、选项、权衡 第二部分:深入探讨上下文学习 — 你现在正在阅读此部分! 第三部分:深入探讨微调
仍在手动审查所有用户与 AI 解决方案的互动吗?
发现如何利用余弦相似度来节省时间并简化你的 AI 系统
·发表于数据科学之路 ·阅读时间:7 分钟·2024 年 10 月 8 日
--
想象一下:
你的 AI 系统终于完成了 **,你对它的成果感到无比骄傲。你发布它,用户开始测试,结果…
现在你陷入困境,需要手动审查每个新查询,以确保解决方案按预期工作。
随着查询越来越多,判断哪些是重复的变得更加困难,耗费了你更多的时间。
图像由作者使用 Dall-E 创建
但是…如果你可以在不动手的情况下自动发现那些从未被问过的新查询呢?
好吧,借助余弦相似度的强大功能,我们可以做到。
这正是我们在这里要讨论的内容——为你提供清晰的理解,告诉你这些系统是如何设置的,以及你需要的工具!目标是给你提供结构,使你能够自信地应用它,节省数小时的试错时间 😃
完全透明:这种方法可能不是唯一的途径,但经过数小时的解决问题,我发现这是最有效的。我将其分解为清晰的步骤,帮助你避免试错。
随机梯度下降背后的数学原理
随机梯度下降的深度解析:算法、假设、优点、公式和实际应用
·发表于 Towards Data Science ·18 分钟阅读·2024 年 1 月 16 日
--
图片来自 DALL-E-2
介绍
上面的图像不仅仅是吸引你阅读本文的一个视觉元素(尽管它的篇幅较长),它还代表了 SGD 算法在寻找全局最小值过程中的潜在旅程。在这个旅程中,它穿越崎岖的路径,其中高度代表了损失。如果现在这一点还不清楚,别担心,等到本文结束时你就能理解了。
索引:
· 1:理解基础概念
∘ 1.1:什么是梯度下降
∘ 1.2:随机梯度下降中的“随机”
· 2:SGD 的机制
∘ 2.1:算法解释
∘ 2.2:理解学习率
· 3:SGD 在实践中的应用
∘ 3.1:在机器学习模型中实现 SGD
∘ 3.2:在 Sci-kit Learn 和 Tensorflow 中的 SGD
· 4:优势与挑战
∘ 4.1:为什么选择 SGD?
∘ 4.2:克服 SGD 中的挑战
· 5:超越基础 SGD
∘ 5.1:SGD 的变种
∘ 5.2:SGD 的未来
· 结论
停止被数据驱动
为什么我们会被数据误导以及如何避免
·发布于 Towards Data Science ·10 分钟阅读·2024 年 8 月 30 日
--
来源:unsplash.com
通常,在基于数据做决策时,我们会觉得自己做出了更聪明、更准确的选择。现实情况却有所不同。从房地产购买到饮食选择再到公司董事会的选择,数据可以是一个出色的催化剂,帮助我们做出最糟糕的决策。
想象数据就像你那位才华横溢却不可预测的朋友。它们有时是天才,有时却一团糟。你会把你心爱的福特福克斯钥匙交给他们吗?还是会礼貌地坚持让他们坐在副驾驶座上?
本文探讨了为什么数据应该牢牢地坐在副驾驶座,而不是掌握方向盘。文章将分析一些真实的案例,在这些案例中,数据驱动的决策可能把我们引入了错误的方向,甚至跌入了虚假信息的悬崖。幸运的是,本文还提供了一些可操作、易于应用的建议,教你如何通过遵循四步法来评估数据驱动洞察的质量,从而避免这种情况:
-
评估数据来源,
-
考虑数据展示者的偏见,
-
认识到数据读取者的偏见,
-
识别数据与洞察之间的任何逻辑失误。
在这段旅程结束时,你将更好地准备好在决策的道路上行驶,同时确保你那位不可预测的朋友安全地系好安全带,坐在副驾驶座上。
TL;DR
-
永远质疑数据来源。如果来源不可靠或未提供,不要害羞,直接拒绝整个分析。
-
不要犹豫,尤其是在遇到违背直觉的主张时,应该进行事实核查。
-
留意数据呈现者的偏见。问问自己,如果得出的结论正好相反,他们是否还会呈现相同的分析。
-
要意识到确认偏误。问问自己,如果得出的结论正好相反,你是否会接受同样的分析。
-
考虑一下该分析是否符合逻辑。仅仅因为两者之间存在关联,并不意味着其中一个是另一个的原因。
-
认识到数据解释通常涉及假设和逻辑跳跃。至关重要的是,要用你自己的判断来识别并评估这些跳跃。
问题一——数据来自哪里?
下面是《旁观者》杂志上一篇文章的摘录,标题是:关于骚乱的非流行真相。文章的主题是 2024 年英国反移民骚乱。
…我决定查一下上周发生最严重骚乱的一些北部城镇的就业统计数据。我还查了 2011 年的统计数据,并将两者进行了对比。提前提醒你,如果你容易感到沮丧,现在最好别继续看下去。
2011 年,桑德兰的失业福利(包括伤残福利)占比为 18%;如今是 19%。2011 年,罗瑟汉姆的失业率为 16%;今天是 18%。哈特尔浦的失业率为 21%;如今是 23%。
— 关于骚乱的非流行真相,《旁观者》
那么,这些数据是从哪里来的呢?
在这个例子中,我们看到的只是“就业统计数据”……那到底意味着什么呢?在英国,并没有一个集中收集就业统计数据的地方,因此作者所指的数据并不显而易见。你可能会选择直接忽视这些数据,从而也忽视文章的其余部分,因为文章的前提就是基于这些数据。然而,我是个不甘心的人,于是我戴上了最好的福尔摩斯帽,开始寻找没有引用的数据。
遗憾的是,我不是福尔摩斯。尽管我进行了大量的网络侦查,还是找不到与文章相匹配的数据。然而,我确实找到了其他的“就业统计数据”。具体来说,我找到了国家统计局(ONS)发布的按地区划分的失业率。它描绘了一幅完全不同的图景。事实上,完全相反的图景!文章中提到的三个自 2011 年以来就业减少的地区,ONS 实际上发现它们的就业率都有所增加!*我恐怕这篇文章揭示的唯一一个非流行的真相,就是新闻媒体的退化。(或者这一直就是这样的吗?)
数据链接:桑德兰, 罗瑟勒姆,以及 哈特尔浦 就业统计数据。
问题二 —— 如果结果相反会怎样?
…通过实验室和实地实验,我们发现,在有作弊机会时,事先签名比事后签名更能突出伦理问题,并显著减少不诚实行为。
— 在开始时签名使伦理问题更为突出,并减少了与在结尾签名相比的不诚实自我报告,莉莎·L·舒(Lisa L. Shu)等人。
这段话出自一篇在行为科学领域具有高度影响力的研究论文的摘要。如果你没有完全理解这段话,因为它采用了经典的学术晦涩风格,那么这段话的意思是:在文件顶部签名会比在底部签名更不容易撒谎。
这些数据来自一群基于备受推崇的大学的研究人员,最著名的包括当时在哈佛商学院任教授的弗朗切斯卡·吉诺(Francesca Gino)。对我而言,这已经回答了我们关于数据的第一个问题(“数据来自哪里?”)。
接下来我们需要问:“如果结果相反,数据还会呈现出来吗?”为了回答这个问题,我们首先需要了解一下展示数据的人员和/或机构。在这个例子中,它是一个行为科学学者小组。学术界是一个竞争激烈的行业,具有极高的失败率。因此,学者们必须尽早被视为“成功”,以避免被淘汰。决定学者是否成功的关键因素,毫无疑问,就是他们的工作。但是,如何衡量他们工作的质量呢?一个常见的方法是引用次数,即一位学者的作品在其他学者作品中被提及的次数。
问题在于,如果你的论文中有意外或有趣的结果,你被引用的可能性会大大增加。回到原来的问题,你认为如果在签署文件时,签署在顶部与底部的群体之间没有诚实度差异,结果会有这么大的影响力吗?不会,因为那会是人们的预期。结论是,学者们有强烈的动机去生成意外或有趣的结果。因此,在我们的例子中,我们应当理解,作者们有强烈的偏见去宣称两组之间存在(显著的)诚实度差异。
这种偏见如此强烈,以至于导致了大多数科学研究无法被同行学者重复验证。无法重复验证意味着一位学者写了一篇研究论文,复制了另一位学者研究论文中的方法/实验,但结果却与原始论文的结果大不相同。回到我们的案例研究作为例子,在这篇研究论文取得巨大成功后,许多其他研究尝试进行相同或类似的实验。所有实验结果都未发现诚实与文件签名位置之间有任何关联。结果发现,原始论文的结论很可能是数据造假的结果。许多由哈佛大学著名教授弗朗西斯卡·吉诺(Francesca Gino)共同撰写的其他有影响力的论文也是如此。
这种“身临其境”的偏见不仅仅存在于学术界。从技术供应商发布与竞争对手的最新表现到投资银行向潜在客户展示自己在排名表上位居第一,偏见无处不在。而且,问题不仅仅在于他人,最强的偏见很可能就是你自己——是的,就是你。通常被称为确认偏误,它指的是倾向于以确认或支持自己先前信念的方式搜索、解读、偏爱和回忆信息。
是否曾想过,为什么这么多人在许多政治问题上如此愚蠢?可悲的是,这更可能是你自己的确认偏误的结果,而非数百万人的集体愚蠢。
如果你想避免自己偏见的陷阱,以及数据呈现者的偏见,你不仅需要问:“如果结果相反,数据还会这样呈现吗?”还要问:“如果结果相反,我会考虑这些数据吗?”这可能是一颗难以下咽的药丸,但克服自己的偏见,可能会比全世界的研究更能启发你。
*可能包含谎言,亚历克斯·埃德曼斯。第 130 页。
第三个问题 — 从数据到洞察的跃迁是否存在缺陷?
对于大多数大学毕业生来说,拥有学位是有回报的。
根据估算,女性一生中如果拥有学位,收入可能会增加约 25 万英镑,而男性这一数字大约是 17 万英镑。
— 让你致富的学位…和那些不能的,BBC 新闻
这个引用来自英国广播公司(BBC)新闻部。具体来说,来自他们的文章《‘让你致富的学位…和那些不能的’》。首先,最重要的问题是:是否有可靠的数据来源?
数据来源:BBC 新闻,让你致富的学位…和那些不能的
好消息是,答案是(可能*)是的!文章引用了另一个著名机构——财政研究所的数据。你可能想深入研究数据来源的具体报告,但从表面分析来看,我对这份原始数据的来源感到满意。
其次,我们要问自己,“如果结果正好相反,这些信息还会被呈现出来吗?”考虑到 BBC(据我所知)没有支持高等教育机构的理由,合理的假设是,如果数据表明没有经济效益,BBC 也会发布一篇文章指出上大学并没有经济上的好处。
然而,不幸的是,我们仍然不能完全相信 BBC 的发现。我们还需要问一个最终的重要问题:“这个分析是否合理?”在查看了文章中关于各学科平均收入的图表后,请尝试在继续下一段之前回答以下问题:“既然拥有医学学位的人收入高于平均水平,假设医学学位是更高薪水的原因是否合理?”
我的回答是:医学学位对于成为合格的医生是必需的,而医生通常收入较高。这支持了医学学位是未来较高收入的主要原因这一观点。另一方面,你不仅需要选择医学,还需要被选中去学医学。考虑到英国医学毕业生的平均 A-Level 成绩为杰出的AAA,你可以说,高薪的原因是因为平均医学专业学生在学术上具有卓越的认知能力,这与他们是否选择医学专业无关。
那么,第二高平均薪资的学位——经济学呢?与医学不同,你并不需要经济学学位才能获得任何特定的高薪工作。我认为,经济学学位不太可能直接增加毕业生未来的薪水。例如,一个真正对钱感兴趣的学生,可能更倾向于根据薪资高低来做职业决策。这个人也可能更倾向于选择大学里的经济学专业。** 如果这是真的,那么让一个对钱不感兴趣的人去学经济学,可能并不会像文章所暗示的那样,对他们未来的职业或财务前景有太大帮助。
类似地,一个获得大学学位的人,可能与没有上大学的人并没有什么显著的区别。它可能只是一个副作用。例如,在英国,富裕家庭的孩子明显更可能上大学。父母的财富可能也会影响孩子的高薪工作前景;例如,他们可能会向孩子传授如何面试、如何爬上光滑的公司阶梯,和/或如何建立人际网络。考虑到这一思考方式,文章得出的结论——“对于大多数大学毕业生,拥有学位是有回报的”——至少不是一个已证实的事实,应该不被视作已定论。
判断什么是“合理的”是主观的;例如,你可能并不认同我上述的论点。然而,重要的是要意识到人们从数据到洞察的假设跳跃,并在接受它们之前进行你自己的“合理性”评估。
*承认我们在调查深度上时间的局限性是非常重要的。对于一些非常严肃的事情,你可能需要深入研究,但作为一个务实主义者,对于我们所看到的每一项数据分析来说,这并非总是可能的。
**你可能在想:“等一下,你并没有提供任何数据来支持这个理论。”你说得对!然而,很多时候我们并没有所有数据可以依赖,我们只能依靠自己的经验和直觉。作为一名经济学毕业生,同时从小对金融充满兴趣,这个理论对我来说是有道理的。
总结
总之,数据不应该成为你决策的驱动力。就像一个无法预测的朋友,数据应当牢牢地坐在副驾驶位置上,最好把儿童锁上。他们可以提供建议,但最终在基于数据做出任何决策之前,你需要保持怀疑态度。
记住,在审视数据、统计和/或研究时,你需要问自己:
-
这些数据来自哪里?
-
如果结果相反,这些数据会被呈现出来吗?
-
如果结果相反,我会考虑这些数据吗?
-
从数据到洞察的跳跃是否存在缺陷?
数据科学家/分析师的最终提醒
对于数据科学家和分析师来说,这些问题不仅仅是一个保障——它们是一项责任。作为数据驱动洞察的守门人,你的角色至关重要,确保决策是基于合理推理,而不仅仅是原始数据和算法。通过应用这种思维方式,你可以避免成为组织中那个不可预测的“朋友”。谁知道,或许有一天你甚至会被邀请进入驾驶座。
停止猜测,衡量你的 RAG 系统以推动真正的改进
提升检索增强生成(RAG)性能的关键指标和技巧
·发表于Towards Data Science ·阅读时间:24 分钟·2024 年 10 月 4 日
--
大型语言模型(LLMs)的进展引起了全世界的关注。随着OpenAI 发布的 ChatGPT在 2022 年 11 月上线,以前鲜为人知的术语如生成式人工智能(Generative AI)进入了公众话语。短短时间内,LLMs 在现代语言处理任务中找到了广泛的应用,甚至为自主 AI 代理铺平了道路。有些人称之为技术的分水岭时刻,并将其与互联网的到来,甚至是电灯泡的发明做出崇高的比较。因此,绝大多数商业领袖、软件开发人员和企业家都在热衷于利用 LLMs 为自己带来优势。
检索增强生成(RAG)作为塑造应用生成式人工智能领域的一个关键技术,正在发挥重要作用。RAG 是 Lewis 等人在其开创性论文Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks中提出的一个新概念,它迅速成为增强大型语言模型输出可靠性和可信度的基石。
在这篇博客文章中,我们将深入探讨如何评估 RAG 系统。但在此之前,让我们通过理解 RAG 的需求并概述 RAG 管道的实施来设定背景。
LLMs 的诅咒
停止手动排序你的 Python 列表,如果性能是你的关注点
图片由作者在 Canva 上制作
一个与 C 扩展一样快速的排序集合库
·发表于 Towards Data Science ·阅读时间:8 分钟 ·2024 年 8 月 29 日
--
至少对我来说,大多数时候在用 Python 编程时,我都在处理集合类型。这包括原生的 Python 列表、集合和字典,以及一些第三方集合类型,如 Numpy 数组和 TensorFlow 张量。后者通常要快得多,因为它们是使用 CPython 或其他与 C 相关的扩展实现的。
然而,有时候使用它们会让人感到不知所措,特别是当我们正在开发一个与数据科学无关的应用时,这些库可能会显得过于庞大,仅仅为了提高性能而引入可能并不划算。
在本文中,我将介绍一个名为 Sorted Containers 的库,当需要一个排序的集合类型时,它提供了最佳实践。我将涵盖这个库中的两种主要类型:排序列表和排序集合。我会通过一些实际的例子来帮助理解。
在开始之前,确保你已经通过pip
安装了这个库。
pip install sortedcontainers
1. 排序列表 — 实时排行榜
数据质量不需要复杂
三种零成本解决方案,只需几个小时,而非几个月
·发布在 Towards Data Science ·阅读时长 8 分钟 ·2024 年 12 月 10 日
--
一个“数据质量”认证的管道。来源:unsplash.com
在我的职业生涯中,数据质量相关的项目通常意味着重大变革。从治理流程到昂贵的工具,再到 dbt 实施——数据质量项目似乎从不想做得小而简单。
更重要的是,使用这种方式修复数据质量问题通常会导致新问题。更复杂、更高的成本、更慢的数据项目发布……
由作者使用 Google Sheets 创建
但事实并非一定如此。
虽然像 Google 和 Meta 这样的大公司由于其庞大的规模和复杂性需要广泛的数据质量框架,但大多数组织可以通过更简单的方法实现优秀的数据质量。
在本文中,我们将探讨三种小型到中型数据项目可以用来快速提高数据质量的方法,同时保持
将复杂性降到最低,新的成本为零。我们开始吧!
简短总结:
-
利用老派的数据库技巧,如 ENUM 数据类型和列约束。
-
为您的特定数据质量问题创建一个自定义仪表板。
-
使用一个简单的 Python 脚本生成数据血统。
利用老派的数据库技巧
在过去的 10 到 15 年里,我们见证了数据行业的巨大变化,尤其是大数据、并行处理、云计算、数据仓库和新工具(大量大量的新工具)。
因此,为了腾出空间容纳所有这些新事物,我们不得不告别一些东西。有一些积极的方面(比如微软 Access),但也有一些最多只能说是值得怀疑的,比如传统的数据设计原则和数据质量及验证过程(数据摄入时)。后者将是本节的主题。
首先,什么是“数据质量和验证在摄入时”?简单来说,这意味着在数据进入表格之前对其进行检查。可以把它想象成夜店外的保安。
替代方案是构建后再测试,这意味着先将新数据放入表格中,然后再检查它。构建后再测试是许多现代数据质量工具选择的方法,包括最流行的dbt。
Dbt 会首先运行整个数据转换管道,只有在所有新数据就位后,才会检查数据是否合格。当然,在许多情况下,这可能是最佳解决方案。例如,如果业务愿意为速度而牺牲质量,或者如果在生产表之前有一个 QA 表(Netflix 称之为写入-审计-发布)的话。然而,仅使用这种数据质量方法的工程师可能错失了对其组织的巨大收益。
生成表格前后进行测试。由作者使用draw.io创建。
测试后再构建比构建后再测试有两个主要优势。
第一个优势是,它确保下游表格中的数据始终符合预期的数据质量标准。这为下游用户提供了一定程度的可信度,这种可信度通常是缺乏的。它还可以减轻负责数据管道的工程师的焦虑感。
我记得当我曾经在一家公司负责一个关键财务管道时。遗憾的是,这个管道非常容易出现数据质量问题,而当时的解决方案是一个构建后再测试的系统,每晚运行一次。这意味着我每天早上必须赶早去检查运行结果,以免下游用户开始查看他们的数据。如果出现任何问题,我就需要迅速修复问题,或者发送一条羞耻的 Slack 消息,通知业务部门数据有问题,请耐心等待我修复。
当然,测试后再构建并不能完全解决这种焦虑问题。故事的转变从需要匆忙修复问题以避免下游用户看到不良数据,变成了匆忙修复问题以避免下游用户看到过时数据。然而,工程师的工作就是权衡不同解决方案的利弊。在这种情况下,我知道,对于业务和我的理智来说,旧数据是两种恶性选择中的最佳方案。
测试-然后构建的第二个好处是,它通常比设置一个完整的 QA 区域更容易实现,尤其是与为解决大多数数据质量问题而准备的“火箭筒打兔子”解决方案相比。你需要做的就是在创建表时包含数据质量标准。看看下面的 PostgreSQL 查询:
CREATE TYPE currency_code_type AS ENUM (
'USD', -- United States Dollar
'EUR', -- Euro
'GBP', -- British Pound Sterling
'JPY', -- Japanese Yen
'CAD', -- Canadian Dollar
'AUD', -- Australian Dollar
'CNY', -- Chinese Yuan
'INR', -- Indian Rupee
'BRL', -- Brazilian Real
'MXN' -- Mexican Peso
);
CREATE TYPE payment_status AS ENUM (
'pending',
'completed',
'failed',
'refunded',
'partially_refunded',
'disputed',
'canceled'
);
CREATE TABLE daily_revenue (
id INTEGER PRIMARY KEY,
date DATE NOT NULL,
revenue_source revenue_source_type NOT NULL,
gross_amount NUMERIC(15,2) NOT NULL CHECK (gross_amount >= 0),
net_amount NUMERIC(15,2) NOT NULL CHECK (net_amount >= 0),
currency currency_code_type,
transaction_count INTEGER NOT NULL CHECK (transaction_count >= 0),
notes TEXT,
CHECK (net_amount <= gross_amount),
CHECK (gross_amount >= processing_fees + tax_amount),
CHECK (date <= CURRENT_DATE),
CONSTRAINT unique_daily_source UNIQUE (date, revenue_source)
);
这 14 行代码将确保 daily_revenue 表强制执行以下标准:
id
- 主键约束确保唯一性。
date
-
不能是未来日期(通过 CHECK 约束)。
-
是与 revenue_source 的唯一约束的一部分。
revenue_source
-
不能为 NULL。
-
是与 date 的唯一约束的一部分。
-
必须是 revenue_source_type 枚举中的有效值。
gross_amount
-
不能为 NULL。
-
必须大于或等于 0。
-
必须大于或等于 processing_fees + tax_amount。
-
必须大于或等于 net_amount。
-
精确的小数处理。
net_amount
-
不能为 NULL。
-
必须大于或等于 0。
-
必须小于或等于 gross_amount。
-
精确的小数处理。
currency
- 必须是 currency_code_type 枚举中的有效值。
transaction_count
-
不能为 NULL。
-
必须大于或等于 0。
这很简单,可靠。你能相信这些功能自 PostgreSQL 6.5 发布以来就已经可用吗……它发布于 1999 年!
当然,没有免费的午餐。通过这种方式强制执行约束确实有其缺点。例如,它使得表格的灵活性大大降低,并且在更新表格时会降低性能。像往常一样,在深入使用任何工具/技术/方法之前,你需要像工程师一样思考。
创建自定义仪表板
我有一个忏悔要说。我曾经认为优秀的数据工程师不会使用仪表板工具来解决问题。我认为一个真正的工程师会查看日志、难以阅读的代码,以及其他任何让他们看起来聪明的东西,如果有人不小心瞥见他们的电脑屏幕的话。
我当时真傻。
事实证明,如果有效地执行并有明确的目的,它们可以非常有价值。此外,大多数 BI 工具使得创建仪表板变得非常简单和快速,而且不需要(太多)时间来学习该工具。
回到我个人的管道经验。我曾经管理一个包含所有业务收入来源的每日汇总表。每个来源来自不同的收入提供者,因此来自不同的系统。有些是通过 API 调用,有些通过电子邮件,还有些通过共享的 S3 桶。正如任何工程师所预期的那样,这些来源有时会出现问题,而由于它们来自第三方,我无法在源头解决问题(只能请求,成功的机会非常有限)。
起初,我只使用故障日志来确定需要修复的地方。问题在于优先级。一些故障需要迅速修复,而另一些则不够重要,不能因此打乱所有工作(我们有些收入来源每天报告的收入只有几分钱)。因此,出现了一些小的数据质量问题的积累,变得难以追踪。
引入 Tableau。
我创建了一个非常基础的仪表盘,展示了过去 14 天按收入来源和日期的元数据。三个指标就是我所需要的:
-
一个绿色或红色的标记,表示数据是否存在或缺失。
-
数据的行数。
-
数据的收入总和。
一个简单但有效的仪表盘。由作者使用Tableau创建
这使得管道的数据质量管理变得更加轻松。不仅我能更快速地查看问题所在,而且它足够用户友好,其他人也能轻松阅读,确保了责任的共享。
实施仪表盘后,业务方关于管道的错误工单几乎降到零,我的中风风险也降低了。
使用血缘图绘制您的数据
简单的数据可观察性解决方案不仅限于仪表盘。
数据血缘图可以帮助快速发现受坏数据影响的表格。
然而,实施起来也可能是一项庞大的任务。
在我看来,造成这一问题的首要原因是 dbt。这个开源工具的一个关键卖点就是它的数据血缘功能。但为了实现这一点,您必须屈服于 dbt 的框架。这包括但不限于:
-
在所有 SQL 文件中实现Jinja3。
-
为每个数据模型创建一个 YAML 文件。
-
通过 YAML 文件添加源数据配置。
-
设置开发和测试流程,例如开发环境、版本控制、CI/CD。
-
基础设施设置,例如托管自己的服务器或购买托管版本(dbtCloud)。
是的,确实很多。
但它不一定非得是这样。归根结底,实现动态数据血缘图所需的就是一台扫描您的 SQL 文件的机器,以及输出用户友好的血缘图的工具。得益于 Python,这可以通过一个仅有 100 行代码的脚本来实现。
如果你了解一些 Python 和 LLM 提示,你应该能在一个小时内修改代码。或者,已经有一个轻量级的开源 Python 工具叫做SQL-WatchPup,它已经包含了代码。
只要您拥有所有的 SQL 文件,在 15 分钟的设置之后,您就应该能够生成像这样的动态数据血缘图:
示例数据血缘图输出。由作者使用SQL-WatchPup创建
就这样。没有服务器托管费用。无需学习额外的编程语言。无需重构文件。只需在本地运行一个简单的 Python 脚本。
结论
说实话——我们都喜欢那些闪亮的新工具,但有时最好的解决方案是那些老旧、不可酷、和/或不流行的工具。
下次当你面临数据质量问题时,先退后一步,再考虑是否需要进行那次大规模的基础设施改造。问问自己:一个简单的数据库约束、一个基础的仪表盘,或者一个轻量级的 Python 脚本,能解决问题吗?
你的理智会感谢你这么做,你公司的预算也会感谢你。
停止过度使用 Scikit-Learn,改用 OR-Tools
许多数据科学家过度使用机器学习,并忽视了数学优化技术,尽管这些技术对你的职业生涯非常有帮助。
·发表于Towards Data Science ·9 分钟阅读·2024 年 1 月 26 日
--
图片由Emilio Garcia提供,来源于Unsplash
你想听我对 2024 年数据科学现状的独特见解吗?
数据科学家过于迷恋机器学习。
对于拿着锤子的人来说,所有的问题看起来都像钉子;对现代数据科学家而言,所有问题似乎都变成了机器学习问题。我们已经变得如此擅长将问题转化为分析和机器学习的语言,以至于有时忘记了还有其他数据科学方法存在。这是一个巨大的遗憾。
在这篇文章中,我将介绍数据科学的另一个分支——数学优化(特别是约束规划)——并展示它如何为你的数据科学家职业生涯增值。
如果你没有扎实的数学背景,请不要被名字吓到。我在大学时也没有学习数学,但得益于谷歌的开源 Python 库OR-Tools
,我发现开始使用数学优化技术出乎意料的简单,我将在这篇面向初学者的文章中介绍它。
停止计数!为什么为指标设置时间限制对于快速且准确的实验至关重要
为什么你的实验可能永远无法达到显著性
·发布于Towards Data Science ·阅读时长:6 分钟·2024 年 7 月 17 日
--
图片由Andrik Langfield提供,来源于Unsplash
介绍
实验通常比较在某种干预下(暴露处理)或没有干预(对照组)后某个事件的发生频率(或其他总和指标)。例如:我们可能会比较购买次数、观看内容的分钟数,或是点击某个行动号召的次数。
虽然这种设置看起来可能很简单、标准且常见,但它仅仅是“常见”。它是一个棘手的分析问题,除非我们对计算该指标的暴露后时间进行限制。
问题
一般来说,对于那些在暴露后仅仅对某个指标求和的指标(“无限制指标”),以下陈述是不成立的:
-
如果我延长实验时间,如果实验有某些效果,我最终会达到显著性。
-
平均处理效应是明确定义的。
-
在计算样本量时,我可以使用正常的样本量计算方法来计算实验的时长。
为了理解这个问题,假设我们有一个指标Y,它是X的累积和,X是一个定义在单一时间单位上的指标。例如,X可能是今天观看的分钟数,而Y则是过去 t 天内观看的总分钟数。假设为离散时间:
其中 Y 是上面描述的实验度量指标,即事件计数,t 是当前的实验时间,i 是单个单位的索引。
假设流量以恒定速率 r 到达我们的实验:
其中 t 是我们的实验已进行的时间段数。
假设每个 X(i,s) 是独立的,并且具有相同的方差(为简化起见;无论是自相关等,类似的问题都会以不同程度的形式出现),但均值不一定是恒定的。那么:
我们开始看到问题。我们度量指标的方差随时间变化并不恒定。事实上,它正在变得越来越大。
在一个典型的实验中,我们构建一个 t 检验来检验处理效应是否为 0,并寻找反对这一零假设的证据。如果找到证据,我们将说实验是统计上显著的成功或失败。
那么,在这种情况下,t 统计量是什么样子的呢?假设均值 Y 为零的假设。
将 n = rt 代入,我们可以用 t 来表示这个表达式,
与任何假设检验一样,我们希望在零假设不成立时,随着样本量的增加,检验统计量应变得越来越大,从而拒绝零假设,接受备择假设。这一要求的一个含义是,在备择假设下,t 统计量的均值应该发散到无穷大。但是……
在时间 t 时,t 统计量的均值只是到时间 t 为止度量指标的均值乘以一个与样本大小或实验持续时间无关的常数。因此,唯一能使其发散到无穷大的方式是如果 E[Y(t)] 发散到无穷大!
换句话说,我们的 t 检验唯一能保证具有任意功效的备择假设是均值为无穷大的假设。有一些备择假设无论样本大小多大都永远不会被拒绝。
例如,假设:
我们显然处于备择假设中,因为极限均值不是零,但 t 统计量的均值收敛到 1,而 1 小于大多数标准临界值。因此,无论我们等待实验多长时间,t 检验的功效永远无法达到 1。我们在具有无限度量指标的实验中看到这一效果,表现为无论实验持续多长时间,置信区间都无法收缩。
如果 E[Y(t)] 确实发散到无穷大,那么 平均 处理效应将无法明确定义,因为度量指标的均值不存在。因此,我们处于一个情景中,要么我们检测平均处理效应的 渐近 功效较低,要么平均处理效应根本不存在。这不是一个好的情形!
此外,这个结果并不是标准样本量分析所假设的。它假设在足够大的样本量下,任何功效水平都能满足固定的、非零的备择假设。而在这里并不成立,因为个体水平的方差并不是恒定的,标准样本量公式中更多假设的是恒定的。方差随着样本量的增加而增加。因此,标准样本量公式和方法对于无限制指标是不正确的。
解决方案
时间限制指标非常重要。我们应该定义一个固定的时间段,在实验暴露后停止计算新的事件。例如,我们可以将指标定义为实验暴露后观看视频的分钟数,而不是定义为实验暴露后观看视频的总分钟数,我们可以将指标定义为在接下来的两天(或其他固定时间)内观看视频的分钟数。
一旦我们这样做,在上面的模型中,我们得到:
时间限制指标的方差不会随着t增加。因此,现在,当我们添加新数据时,我们只会增加更多的观测值。我们不会(在几天后)更改现有用户的指标,也不会增加个体水平的指标方差。
除了统计上的好处外,时间限制指标还使得跨不同持续时间的实验更容易进行比较。
模拟
为了展示这个问题的实际情况,我在下面的数据生成过程中比较了无限制版本和时间限制版本的这些指标:
在这里,关注的指标是Y(i,t),如上所定义:无限制情况下的X的累积和,以及时间限制情况下的X在时间d时的和。我们设置了以下参数:
然后我们模拟数据集,并计算Y的均值,在指标时间限制为两段时间(d=2)和无限制情况下,检验其均值是否为零的零假设。
在这两种情况下,我们都处于备择假设中。无限制情况下Y(i,t)的长期均值为:0.2。
我们将显著性水平设定为 0.05,并在这两种情况下考虑检验的功效。
从图 1 可以看出,尽管样本量增加了 10 倍,但无限制指标的功效始终没有提高。时间限制指标在相同样本量下接近 100%的功效。
图 1. 非零备择假设的功效模拟(图片由作者提供)
如果我们不对计数指标设置时间限制,即使存在效应,我们可能也没有足够的功效去发现,即使我们运行实验的时间很长。
结论
限制指标时间是一个简单的操作,但它使得我们作为实验者非常希望成立的三件事成为现实:
-
如果存在效果,我们最终会达到统计显著性。
-
平均处理效应是明确的,其解释在整个实验过程中保持不变。
-
常规样本大小方法有效(因为方差并非持续增加)。
作为附带好处,时间限制性度量通常因另一个原因增加了效能:它减少了实验暴露后很长一段时间的冲击方差(因此,更不可能与实验相关)。
扎克
联系方式:linkedin.com/in/zlflynn/
。
停止浪费 LLM 令牌
将输入批量处理可以在不影响性能的情况下带来显著的节省
·发布于Towards Data Science ·阅读时间:5 分钟·2024 年 8 月 7 日
--
如果你使用 LLM 来标注或处理更大的数据集,很可能你并没有意识到自己浪费了大量的输入令牌。当你反复调用 LLM 处理文本片段或整个文档时,你的任务指令和静态的少样本示例会在每个输入示例中重复出现。就像将盘子整齐地堆叠能节省空间一样,将输入批量处理可以带来显著的节省。
假设你想要标注一个较小的文档库,包含 1000 个单页文档,并且每个文档的指令和少样本示例约为半页长。分别标注每个文档将花费大约 100 万个输入令牌。然而,如果你在同一次调用中标注十个文档,你将节省大约30 万个****输入令牌(即节省 30%),因为我们不需要重复指令!正如下面的示例所示,这通常在性能损失最小(甚至可能获得性能提升)的情况下发生,特别是当你同时优化提示时。
通过小批量处理来节省令牌
在下面的图中,我假设我们平均文档长度为D个令牌,指令和少样本示例的长度为rD个令牌。我在前一段中提到的示例场景,即指令长度为文档的一半(r* = 0.5),出现在下方的蓝色曲线中。对于更长的共享指令,我们的节省可能会更高:
主要的收获是:
-
即使是相对简短的指令(蓝线),小批量处理依然具有价值
-
并不一定需要使用非常大的小批量大小。大多数节省可以通过适中的小批量大小(B ≤ 10)获得。
实际操作中的小批量
让我们实际操作一个任务,我们希望对文本片段进行分类,以便进行进一步分析。我们将使用来自自然指令基准的一个有趣任务,在该任务中,我们需要将辩论中的句子标注为四个类别之一(价值、事实、证词或政策)。
看一个示例,我们看到我们首先获取当前主题作为上下文,然后需要对相关句子进行分类。
{
"input": {
"topic": "the fight for justice,equality,peaceand love is futile",
"sentence": "What matters is what I am personally doing to ensure that I am filling the cup!"
},
"output": "Value"
}
我们尚未回答的一个问题是:
我们如何选择正确的小批量大小?
之前的工作表明,最佳的小批量大小取决于任务和模型。我们实际上有两个选择:
-
我们选择一个合理的小批量大小,比如 5,并希望我们不会看到任何下降。
-
我们优化小批量大小以及其他选择,例如少量示例的数量。
正如你可能已经猜到的那样,我们将在这里选择第二种方案。为了运行我们的实验,我们将使用开源框架SAMMO,这是一个用于 LLM 调用和提示优化的框架。
在 SAMMO 中,提示被编码为提示程序(这些程序只是嵌套的 Python 类,接收输入数据后被调用)。我们将任务分成三个部分,并以 JSON 格式组织小批量。
def prompt_program(fewshot_data, n_fewshot_examples=5, minibatch_size=1):
return Output(
MetaPrompt(
[
Section("Instructions", task["Definition"]),
Section(
"Examples",
FewshotExamples(
fewshot_data, n_fewshot_examples
),
),
Section("Output in same format as above", InputData()),
],
data_formatter=JSONDataFormatter(),
render_as="markdown",
).with_extractor(on_error="empty_result"),
minibatch_size=minibatch_size,
on_error="empty_result",
)
如果不使用小批量,并且使用五个少量示例,我们得到的准确率为 0.76,并且需要支付58255 个输入令牌。
现在,让我们探索小批量如何影响成本和性能。由于小批量减少了总输入成本,我们现在可以使用这些节省的成本来添加更多的少量示例!我们可以通过在 SAMMO 中设置搜索空间来研究这些权衡:
def search_space(fewshot_data):
minibatch_size = search_op.one_of([1, 5, 10], name="minibatch_size")
n_fewshot_examples = search_op.one_of([5, 20], name="n_fewshot")
return prompt_program(fewshot_data, n_fewshot_examples, minibatch_size)
运行这个实验向我们展示了完整的权衡:
setting objective costs parse_errors
--------------------------------------- ----------- --------------------------------- --------------
* {'minibatch_size': 1, 'n_fewshot': 5} 0.76 {'input': 58255, 'output': 5817} 0.0
{'minibatch_size': 1, 'n_fewshot': 20} 0.76 {'input': 133355, 'output': 6234} 0.0
{'minibatch_size': 5, 'n_fewshot': 5} 0.75 {'input': 15297, 'output': 5695} 0.0
{'minibatch_size': 5, 'n_fewshot': 20} 0.77 {'input': 30317, 'output': 5524} 0.0
{'minibatch_size': 10, 'n_fewshot': 5} 0.73 {'input': 9928, 'output': 5633} 0.0
* {'minibatch_size': 10, 'n_fewshot': 20} 0.77 {'input': 17438, 'output': 5432} 0.0
因此,即使使用 20 个少量示例,我们仍然节省了近70%的输入成本([58255–17438]/58255),并且保持了整体准确性!作为练习,你可以实现自己的目标,自动考虑成本,或在搜索空间中包括不同的数据格式化方式。
注意事项
所有这些背后的隐含前提是:(i)我们有足够的输入示例使用共享的指令,(ii)我们在延迟方面有一定的灵活性。第一个假设在许多标注场景中是成立的,但显然在一次性查询中不成立。在标注或其他离线处理任务中,延迟并不是特别关键,因为吞吐量最为重要。然而,如果你的任务是尽可能快地给用户提供答案,那么发出B个并行调用可能比一次调用B个输入示例更有意义。
结论
正如这个快速且实用的示例所示,同时对 LLM 进行多个输入提示可以在更好或相当的准确度下大大降低成本。好消息是,即使在适中的小批量大小下(例如 5 或 10),节省的成本也可能相当可观。借助 SAMMO,你可以自动查看在不同选择下性能的表现,从而做出最佳选择。
一个开放的研究问题是如何将此与检索增强生成(RAG)结合——可以通过对所有检索的示例取并集,或以某种方式对其进行排名。SAMMO 让你在构建提示时探索这些策略中的一些,同时还有很多其他选择,例如如何格式化输入数据。如果你希望查看更多关于这个话题或其他任何内容,请留下评论。
免责声明:**我是 SAMMO 的作者,这是一个开源的 MIT 许可证框架,用于提示优化。
资源
-
此示例的代码: Notebook 文件 或 在 MyBinder 上实时运行
-
阅读: SAMMO 用户指南 和 arXiv 上的论文,其中有更多细节
为机器学习面试制定准备策略
解码职位角色并识别重点领域
·发表于 Towards Data Science ·阅读时长 8 分钟 ·2024 年 8 月 7 日
--
在我职业生涯初期,在准备面试时经历了多次失败。每一次失败都让我学到了宝贵的经验,最终帮助我获得了梦寐以求的 Meta 机器学习工程师职位。成功的关键并不是天赋或运气,而是持续的学习和有针对性的准备。
在许多关于机器学习面试的文章中,你会找到关于面试轮次和典型问题的描述,但我发现它们在战略性准备指导方面往往有所不足。这并不意味着它们没有帮助,但有时会导致遇到意料之外的问题。
理解机器学习角色的广泛范畴——通过工作职责和专业化领域——可以显著提升你的面试策略、增加你的信心,并帮助减少不确定性。这些差异往往仅从职位名称中无法显现,因此识别它们将为你提供宝贵的洞察,使你能够更精准地应对下一个机器学习面试。让我们一起来探讨这个范畴:
机器学习角色的范畴
作者图片:机器学习角色范畴中的职位名称示例。不幸的是,每家公司对这些职位名称的定义不同,因此查看职位描述至关重要。
ML 角色的技术职责和专业领域可以有很大差异。
1) 技术责任:
数据分析 / 建模:
- 技能:数据分析、特征工程、模型开发与训练、统计分析、实验设计。
机器学习服务和基础设施:
- 技能:训练和推理服务、可扩展性、模型部署、API 集成。
2) 专业领域:
通才:
- 技能:在各种问题领域中工作,运用广泛的机器学习技术,并适应团队的不同需求。
专家:
- 技能:在所选领域的深厚专业知识(如自然语言处理(NLP)、计算机视觉(CV)或行业特定领域,如自动驾驶汽车和机器人技术),以及对领域特定工具的高级知识。
解读职位描述
现在你已经理解了机器学习职位的范围,你可以更好地从职位描述中识别出该职位的真正职责。我曾经在一家自动驾驶公司面试,职位的重点是计算机视觉和传感器融合。尽管我在一般机器学习算法方面有很强的背景,但我没有准备好回答关于卷积神经网络(CNN)和核函数的具体问题。这段经历让我意识到理解职位描述中隐藏的要求的重要性。
理解职位要求对于两个主要原因至关重要:
-
这有助于你排除那些不符合你目标的职位。
-
它提供了关于该职位所涉及特定领域的线索。
以下是我如何在职位描述中识别关键字,以将职位与机器学习领域匹配的一些例子。
图片来自作者:示例职位描述,摘自公开的 Google 职位搜索工具,并突出显示关键词,以识别核心的机器学习职责。
注意: 职位描述通常缺乏细节,因此如果不清楚,始终向招聘人员寻求更多信息。
现在你已经从职位描述中找出了要求,接下来可以开始考虑你的准备策略。但在此之前,我们先来看看机器学习面试中最常见的几种面试轮次。
机器学习面试轮次
在深入准备不同角色的策略之前,让我们快速了解机器学习面试中的 4 种不同类型的面试轮次。
-
机器学习基础/广度:这一轮考察工程师对机器学习基础知识的理解,涵盖多个主题。通常是快速问答环节,面试官可能会在不同主题之间跳跃,或者在某个领域内提出一般性问题。
-
机器学习案例研究/深度:这一轮侧重于专门领域和详细的案例研究,来自你的过去项目和/或特定领域知识。这一轮尤其有趣,是所有面试类型中最开放的,通常面向具有一定经验的中级职位。
-
机器学习系统设计:这一轮面试类似于典型的软件工程系统设计面试,应用相似的原则。你会被给定一个产品领域(例如,设计一个 YouTube 推荐系统的机器学习模型),并要求你定义问题、概述设计过程,并传达你的思路,包括权衡。面试官会关注你的问题解决方法、思维过程和高层设计能力。
-
机器学习编程: 这一轮并不常见,但在初创公司中更为常见。策略很简单:将机器学习编程准备与机器学习基础知识结合起来,练习基本模型的编程。
你可以在这篇文章中找到更多的细节和资源,以帮助你为这些面试环节做准备(article1)。
面试准备策略
既然你已经了解了面试的各个环节,接下来让我们讨论如何为你所针对的特定职位制定准备策略的步骤。
1. 从基础开始
确保你扎实掌握基础知识(例如主题列表),你甚至可以在申请面试之前就开始准备这些内容。无论你目标的是哪个机器学习角色或层级,这个基础都是至关重要的。
作者提供的图像:我基于核心机器学习职责和职位层级的最关键面试环节的经验。请注意,这并不意味着其他环节不重要,但它给出了一个关于在哪里集中准备的方向。
2. 确定你的策略
确定你计划申请的职位和角色,并为此进行专门的准备。
数据/建模角色:
- 注意公司/职位特定的基础知识
每个公司和职位都是独特的,它们的要求以及你在面试中可能会遇到的问题类型也各不相同。我在为一家房地产公司面试初级机器学习工程师职位时亲身体验到了这一点。他们问我关于回归树以及连续变量的分割标准——这是我没有准备的主题,因为我的背景是在自然语言处理(NLP)和分类问题上。事后看来,很明显,考虑到这家公司专注于房屋销售和价格预测,回归问题是很常见的。
提示: 确定该职位是通才还是专才:
— 通才角色: 通常需要掌握机器学习基础和深度学习知识,包括多层感知器、反向传播、卷积神经网络(CNNs)、递归神经网络(RNNs)和长短时记忆网络(LSTMs)。
— 专才角色: 例如,在自然语言处理(NLP)中,要熟悉像 word2vec 这样的技术。在一次 NLP 面试中,我特别被问到了 word2vec 的基本理论。
了解职位和团队的具体要求将帮助你定制准备策略,并提高成功的机会。
2. 准备领域特定的知识
随着职位的专业化程度提高,重点会转向特定领域的知识和深度。注意,当你目标是更高层次的职位时,专业化的期望会更加明显。
一些常见的专业化领域可以思考一下:
-
排名/推荐: 对于搜索(例如:谷歌、亚马逊)和发现(例如:Facebook、Instagram、Netflix)至关重要。这些角色通常提供最多的机会。我建议每个机器学习科学家/工程师都要理解排名和推荐系统,因为有很多职位空缺。
-
广告: 了解与广告相关的挑战,如校准和竞价。广告系统需要平衡最大化收入与保持用户体验之间的关系。像谷歌和 Pinterest 这样的公司在优化点击率和转化率等因素方面投入大量资金,确保广告既对广告商有效,又对用户具有相关性。
-
自然语言处理(NLP): 了解变压器(transformers)、注意力机制(attention mechanisms)和大型语言模型(LLMs)。自然语言处理支持像谷歌搜索自动完成、亚马逊的 Alexa 和苹果的 Siri 等功能。最近,随着 ChatGPT 和 LLMs 的出现,自然语言处理变得非常抢手。
-
计算机视觉: 了解卷积神经网络(CNNs)、递归神经网络(RNNs)、长短期记忆网络(LSTMs)、图像特征表示、物体检测和分类。计算机视觉应用广泛,从人脸识别到自动驾驶。
3. 研究公司博客和论文
许多公司都有机器学习博客,提供对其工作的洞察。我关注的一些热门博客:
-
Google AI、Pinterest Engineering、Meta AI、Netflix Research、Amazon Science、AWS ML、Microsoft Research、Snapchat Engineering、Uber Engineering、Doordash Eng blog。
-
与你面试的团队或你感兴趣的领域相关的文章,有助于提供他们的挑战和潜在的面试问题的洞察。讨论这些话题也能与面试官激发有价值的对话。
-
提示: 与面试官进行有意义的对话,特别是在面试接近尾声时,有时(但并非总是)能够留下积极的印象,弥补其他方面一般的面试表现。这能够展示你进行研究的能力以及对他们问题领域的理解。
机器学习服务和基础设施角色:
- 注意公司/职位特定的技术栈
对于机器学习服务和基础设施角色,机器学习系统设计环节变得至关重要。这些面试通常关注与你所面试的团队或公司相关的技术栈和系统。
示例:
-
流媒体服务(例如 Netflix): 学习视频推荐系统、流数据处理和内容分发网络(CDNs)。
-
搜索/推荐类岗位(如 Google、Amazon、Doordash、Instagram): 聚焦于用户内容推荐和常见问题,例如“在外卖应用上推荐餐馆”或“设计用户推荐流”。提示: 设计推荐系统组件是机器学习系统设计面试中最常被问到的问题之一。
-
广告(例如 Pinterest、Snapchat、Facebook、YouTube): 了解广告排名及相关挑战,如多阶段排名、实时竞价和用户细分。
推荐这门课程来全面准备机器学习系统设计:Educative.io 的机器学习系统设计
2. 理解领域特定的权衡
就像传统的软件工程一样,机器学习服务和基础设施也有其自身的权衡。在面试中展示你对这些权衡的理解,可以突出你的清晰思维和问题解决能力。
示例:
在一次面试中,我被要求讨论实时推理与批处理推理的利弊。这些权衡通常可以分为两个主要维度:
-
延迟: 低延迟的使用场景,如用户推荐流和搜索,需要实时推理以提供即时结果。
-
成本: 高成本的使用场景通常选择批处理推理,以减少服务器费用,例如垃圾邮件检测、图像分析和财务报告等系统,这些系统处理大量数据。
了解何时使用每种方法至关重要。例如,实时推理对于需要即时响应的应用非常重要,如推荐系统和搜索。相比之下,批处理推理更适合可以周期性处理的任务,如垃圾邮件检测或财务分析,其中成本效率是优先考虑的。
结论
针对性准备对于机器学习面试非常重要,因为它可以:
-
帮助你思考职业目标和兴趣领域
-
满足特定角色和公司的需求
-
有助于理解特定领域的细微差别
-
增加对问题领域的信心,并提高成功的机会
通过专注于这些领域,你可以更自信地应对下一个机器学习面试。
📈 结束语:追踪你的进展
在准备机器学习面试的过程中,跟踪你的进展和学习至关重要。保持一个日志,或者使用数字工具来记录:
-
之前面试中的问题
-
你学习过的论文/博客
-
你研究的关键要点
持续的跟踪不仅帮助你保持组织性,还能提高你的信心,因为你可以看到自己的知识和技能在不断成长。花了我一段时间才意识到其价值,但现在我一直在使用 Google 文档来记录这些内容。
记住,机器学习研究进展迅速,新的突破可能会改变面试问题,因此跟踪最新动态是关键。
祝你面试准备顺利,记得一直保持学习!
周末好书推荐
从这篇文章开始,我将分享一些不错的读物。其中一些可能是面向初学者的,而另一些则会稍微更为高级。但所有这些文章都是为了向对机器学习感兴趣的人提供信息。
-
微调 Llama 3.1 by Maxime Labonne
-
做好比完美更重要 by Torsten Walbaum
-
Pytorch 与 TensorFlow 的区别 by Vineet Dhanawat
如果这篇文章对你有所帮助,并且你想了解更多关于机器学习的实际技巧,欢迎订阅我的新闻通讯,在 Medium 上关注我,或者在LinkedIn上与我联系。我对机器学习充满热情,这是我与你分享这份热情和切实可行建议的方式,无论你是刚入门还是已经是一位有经验的专业人士。
免责声明:本博客基于个人经验和公开资源。请注意,所表达的观点仅代表我个人,不代表我过去或现在雇主的立场。有关最准确的信息,请始终参考雇主公司的官方资源和指南。
草莓悖论:当完美的答案不足以解决问题时
AI 进展如何将责任转移到人类判断上
·发表于Towards Data Science ·阅读时间:9 分钟·2024 年 9 月 25 日
--
你还记得你在哪儿得知 OpenAI 发布了最新模型o1-preview(被称为草莓)吗?
那时我正在与诺贝尔奖得主、博弈论学者罗杰·迈尔森共度一个下午。自然而然,我们的大部分对话围绕着人类与技术的关系展开。
在这篇博客文章中,我将与您分享我们的想法。
这是我(左)和罗杰·迈尔森(右)在 o1-preview 发布当天的合影。
每当一个新型 AI 能力像草莓这样的亮眼产品出现时,互联网上总会响起一片喧嚣,来自各方的炒作者和挑剔者会争论它到底有多好(或者不好)。如果你是来寻找这些讨论的,那就滚动一下,查看我为你制作的视频演示。但这不是我和罗杰谈论的内容。相反,我们直接跳到了每一次 AI 发布后的逻辑结论,问道:
“试想,如果 AI 变得如此强大,你可以立即得到任何你想问的问题的答案,或是对任何请求的即时回应。那么,在这样的世界里,什么是值得教授的?”
我们的答案?我们几十年来一直在努力教授的那个东西。最重要的……
河流排序:为什么有时地理科学家需要在地图上对河流进行排名
学习如何在矢量图层上获取 Strahler 或 Shreve 排序
·发布于面向数据科学 ·6 分钟阅读·2024 年 2 月 14 日
--
预览图片(由作者提供)
亲爱的读者,在本文中,我将深入探讨一个令人兴奋的水文学话题。我将从这张图片开始:
图 1. 河流在地图上的两种表示方式(a 与 b)(图片来源:作者)
免责声明:本文旨在为面临使用地理信息系统(GIS)对河流矢量图层进行排名问题的地理学家以及那些曾在地图上看到“漂亮河流”但不完全知道它们如何制作的人们提供帮助。我们将一起逐步探索空间数据如何在 GIS 应用中表示,河流网络结构如何分析,以及可以使用哪些可视化技术。
现在问题是:哪一张地图看起来更美观?——对我来说,下面的那一张(b)。
实际上,从常识的角度来看,第二种可视化方式也更为准确。流入河床的支流越多,河流就越宽广和充盈。例如,世界上最大的一条河流之一——尼罗河,在其源头(高山地区)几乎看不出是一条强大的河流;而在其河口,随着每一公里向海洋流去,河流吸收了越来越多的支流,水量也变得越来越充沛。
上方所示的地图(图 1b)是基于河流网络的结构信息制作的。在这篇文章中,我将讨论如何获取有关河流的这些附加信息,以及可以为此目的使用的工具。
什么是河流
让我们从解释河流信息如何表示开始。在制图学和地理科学中,河流通常作为一个线性矢量图层表示:每个河流段以带有某些特征的线条表示。例如,段长、地理坐标(对象的几何形状)、地面类型、平均深度、流速等(动画 1)。
动画 1. 空间对象的线性矢量图层。重要提示:个别线性段的几何形状可以通过大量点来定义,而不是仅仅通过两个点(作者提供)。
所以,通常来说,如果你在地图上看到一条河流,你会看到一组这些简单的几何原始元素(属性表中的单独行),它们组合成一个大的系统。可以使用不同的颜色来可视化存储的特征(见图 2)。
图 2. 使用颜色可视化河流段(图片来源:作者)
可视化使用的工具可以是编程语言或专门的应用程序,如ArcGIS(专有软件)或QGIS(开源软件)。
河流结构
关于河流的属性表中的信息可以通过不同的方式收集:来自遥感数据、探险、测量仪器和水文站。然而,关于河流结构的信息通常是在专家看到地图上整个系统的样子时,最后一刻才会指定。例如,研究人员可以自行在矢量图层描述中添加一列,给每个河段分配一个等级(见图 3)。
图 3. 添加新字段并使用大小进行可视化(图片来源:作者)
现在我们可以看到,这张图像与文章开头的地图(图 1b)相似。但问题随之而来:可以使用什么原则来分配这些值?——答案是:有很多。水文中有几种公认的水流等级系统——请参阅水流等级维基页面或论文Stream orders。以下是我在工作中使用的一些方法(见图 4)。
图 4. 水文中流域排序的几种方法(图片来源:作者)
为何如此
现在是时候回答为什么要使用这种排序系统的问题了。我们可以区分两个原因:
-
可视化——使用等级作为线性对象在地图上的大小属性,可以创建漂亮的地图(见图 1);
-
后续分析。
可以将河流网络结构的知识与其他特征相结合,进行进一步分析,例如识别以下模式(图 5)。
图 5. 流速与 Shreve 顺序的关系(图像来源:作者)
如何使用现有工具分配流域顺序
对于大型系统,手动排名是非常困难的,因此已经创建了专门的流域顺序工具。有两种根本不同的方法:
-
使用栅格数据(数字高程模型)进行流域顺序分配;
-
在矢量图层上进行流域顺序分配。
上面,我们描述了如何给矢量图层分配顺序。然而,空间数据通常以另一种格式表示——栅格(矩阵)(图 6)。数字高程模型(每个像素有特定大小的矩阵,例如 90 米乘 90 米,并且每个单元格中存储的海拔值)特别常见。
图 6. 数字高程模型(DEM)作为栅格图层。常用于计算流向并进而确定流域顺序(图像来源:作者)
栅格图层(数字高程模型 — DEM)用于计算流向矩阵和流量积累。例如,ArcGIS 中的流域顺序(空间分析)工具就是基于这一原理工作。在本文中,我不会详细描述这种算法的工作原理,因为在官方文档中有很好的可视化和描述(如果你想了解更多,请查看流向功能页面)。以下是我列出的一些可以使用栅格数据来获取 Strahler 顺序的工具:
-
ArcGIS: 使用 ArcGIS 从数字高程模型(DEM)计算流域顺序;
-
QGIS: 在 QGIS 中计算 Strahler 流域顺序;
-
GRASS: r.stream.order 工具。
然而,这一切都需要大量的栅格数据处理。如果你已经有了一个矢量图层,该怎么办呢?(例如,如果你有一个从 OpenStreetMap 加载的河流网络矢量图层,就会出现这种情况。)接下来我会告诉你!
如何在 QGIS 中获得 Shreve、Strahler 和拓扑顺序的矢量图层
四年前在我们的工作中,我和同事们提出了一种算法,它允许我们仅基于矢量图层和最终点(河流系统的终点,流入湖泊/海洋/海洋的地方)来计算Shreve、Strahler 和 Topological 顺序。该算法的第一个版本在我在 Medium 上的第一篇文章中进行了描述:“基于图形的河流网络段排名算法(用于地理信息分析)”(擦去一滴怀旧的泪水)。最近,我终于有时间写了一些更清晰的文档并准备了插件更新。
在 QGIS 中进行水流排序时,可以使用 Lines Ranking 插件。使用时,需要加载矢量图层,将其重新投影到所需的度量投影,并指定最终点(你可以直接点击地图)——将获得以下结果(图 7)。
图 7. 使用矢量图层和 QGIS Lines Ranking 插件对鄂毕河进行的拓扑水流顺序
现在,亲爱的读者,你已经更深入地了解了水文学中的水流排序主题,并学会了如何使用不同的工具从原始数据(栅格或矢量)中获得它。一旦掌握了河流结构的信息,你就可以制作出美丽清晰的可视化,或者通过将获得的信息与其他特征结合,继续进行分析。
有用的链接:
-
Lines ranking 插件文档:
linesranking.readthedocs.io/en/latest
关于水文学中水流顺序的讲座由 Mikhail Sarafanov 提供
使用 Python、Kafka 和 Faust 进行流处理
如何在高吞吐量时间序列数据上进行流处理并应用实时预测模型
·发表于Towards Data Science ·阅读时长:7 分钟·2024 年 2 月 18 日
--
大多数流处理库并不适合 Python,而大多数机器学习和数据挖掘库却是基于 Python 的。尽管Faust库旨在将 Kafka 流处理理念引入 Python 生态系统,但在易用性方面可能会带来挑战。本文件作为一个教程,提供了有效使用 Faust 的最佳实践。
在第一部分,我介绍了流处理概念的基本概述,广泛借鉴了《设计数据密集型应用》一书1。随后,我探讨了 Faust 库的关键功能,特别是 Faust 窗口,这部分通常在现有文档中难以理解且难以高效利用。因此,我提出了一种通过利用库自身函数的替代方法来使用 Faust 窗口。最后,我分享了在 Google Cloud Platform 上实现类似管道的经验。
流处理
流(stream)指的是随着时间推移逐步可用的无界数据。事件(event)是一个小型的、独立的对象,包含某一时刻发生的事情的详细信息,例如用户交互。事件由生产者(producer)生成(例如温度传感器),并可能被一些消费者(consumers)消费(例如在线仪表盘)。传统的数据库不适合存储高吞吐量的事件流。这是因为消费者需要定期轮询数据库以识别新事件,从而产生显著的开销。相反,最好是让消费者在新事件出现时收到通知,而消息系统(messaging systems)正是为此而设计的。
消息代理(message broker)是一种广泛采用的消息传递系统,其中生产者将消息写入代理,消费者由代理通知并接收这些消息。基于AMQP 的消息代理(AMQP-based message brokers),如RabbitMQ,常用于服务之间的异步消息传递和任务队列。与数据库不同,它们采用瞬时消息的理念,仅在消息被消费者确认后才会删除消息。当消息处理变得资源密集时,可以通过使用多个消费者以负载均衡的方式从相同主题读取来实现并行处理。在这种方法中,消息会被随机分配给消费者进行处理,这可能导致处理的顺序与接收的顺序不同。
另一方面,基于日志的消息代理(log-based message brokers),如Apache Kafka,将数据库存储的持久性与消息系统的低延迟通知能力结合在一起。它们利用分区日志结构,其中每个分区表示按追加顺序存储在磁盘上的记录序列。这一设计使得重新读取旧消息成为可能。Kafka 中的负载均衡是通过将每个消费者分配给一个分区来实现的,从而消息处理的顺序与接收的顺序一致,但消费者的数量受限于可用分区的数量。
流处理(stream processing)涉及对流执行操作,如处理流并生成新的流、将事件数据存储在数据库中或在仪表盘上可视化数据。流分析(stream analytics)是一个常见的使用案例,其中我们在定义的时间窗口内聚合来自一系列事件的信息。滚动窗口(Tumbling windows,非重叠)和跳动窗口(Hopping windows,重叠)是流分析中常用的窗口类型。流分析使用案例的例子可以是简单地计算过去一小时内的事件数,或者对事件应用复杂的时间序列预测模型。
流分析面临着区分事件创建时间(事件时间)和事件处理时间的挑战,因为事件处理可能由于排队或网络问题引入延迟。基于处理时间定义窗口是一种更简单的方法,特别是当处理延迟较小时。然而,基于事件时间定义窗口则更具挑战性。这是因为无法确定窗口内的所有数据是否已经接收完毕,或者是否还有未处理的事件。因此,需要处理在窗口被认为已完成后仍然到达的滞后事件。
在涉及复杂流分析的应用中,如时间序列预测,通常需要将一系列有序的消息作为一个整体在窗口内进行处理。在这种情况下,消息之间存在强烈的相互依赖关系,导致很难从代理中确认并移除单个消息。因此,基于日志的消息代理成为了一种更可取的选择。此外,在这种情况下,由于窗口中的所有消息需要一起考虑,平行处理可能不可行或实现过于复杂。然而,应用复杂的机器学习模型来处理数据可能需要大量计算资源,因此必须采取替代的平行处理方法。本文旨在提出一种解决方案,以在高吞吐量流处理应用中有效地使用资源密集型机器学习模型。
Faust 流处理
有多个流处理库可供选择,例如 Apache Kafka Streams、Flink、Samza、Storm 和 Spark Streaming。每个库都有自己的优缺点,但其中许多并不是特别适合 Python。不过,Faust是一个基于 Python 的流处理库,使用 Kafka 作为底层消息系统,旨在将 Kafka Streams 的理念引入 Python 生态系统。不幸的是,Faust 的文档可能会让人困惑,源代码也可能难以理解。例如,理解 Faust 中窗口的工作方式在不参考复杂的源代码的情况下是具有挑战性的。此外,Faust(v1)和Faust-Streaming(v2)仓库中存在许多开放问题,解决这些问题并非一件简单的事情。接下来,将提供有关 Faust 底层结构的必要知识,并附上代码片段,帮助有效利用 Faust 库。
使用 Faust 的第一步是创建一个应用并配置项目,通过指定代理和其他必要的参数。一个有用的参数是table_cleanup_interval
,将在后续讨论。
app = faust.App(
app_name,
broker=broker_address,
store=rocksdb_address,
table_cleanup_interval=table_cleanup_interval
)
然后,你可以使用agent装饰器定义一个流处理器,从 Kafka 主题中消费数据,并对每个接收到的事件执行某些操作。
schema = faust.Schema(value_serializer='json')
topic = app.topic(topic_name, schema=schema)
@app.agent(topic)
async def processor(stream):
async for event in stream:
print(event)
为了在流处理器中保持状态,我们可以使用 Faust 的Table。表是一个分布式的内存字典,由 Kafka 变更日志主题支持。你可以将table
视为一个可以在流处理器中设置的 Python 字典。
table = app.Table(table_name, default=int)
@app.agent(topic)
async def processor(stream):
async for event in stream:
table[key] += event
Faust 窗口
让我们考虑一个时间序列问题,每秒我们需要从前 10 秒钟的样本中进行预测。因此,我们需要 10 秒重叠的窗口,重叠时间为 1 秒。为了实现这个功能,我们可以利用 Faust 的windowed tables,但在 Faust 文档中对它们的解释不够充分,常常导致困惑。
理想情况下,流处理库应该自动执行以下任务:
-
为每个窗口保持状态(事件列表);
-
确定新事件的相关窗口(最后 10 个窗口);
-
更新这些窗口的状态(将新事件附加到它们各自列表的末尾);
-
在窗口关闭时应用一个函数,使用窗口的状态作为输入。
在下面的代码片段中,你可以观察到 Faust 文档中建议的构建窗口并在流处理器中使用它的方法(参考 Faust 库中的这个示例):
# Based on Fuast example
# Do not use this
window_wrapper = app.Table(
table_name, default=list, on_window_close=window_close
).hopping(
10, 1, expires=expire_time
)
@app.agent(topic)
async def processor(stream):
async for event in stream:
window_set = window_wrapper[key]
prev = window_set.value()
prev.append(event)
window_wrapper[key] = prev
在提供的代码中,window_wrapper
对象是WindowWrapper类的一个实例,提供了一些所需的功能。expires
参数决定了窗口生命周期的持续时间,从创建开始计算。一旦这个指定的时间过去,窗口就被视为关闭。Faust 会定期检查table_cleanup_interval
持续时间,以识别已关闭的窗口。然后,它会应用window_close
函数,使用窗口状态作为输入。
当你调用window_wrapper[key]
时,它返回一个类型为WindowSet的对象,该对象内部包含所有相关的窗口。通过调用window_set.value()
,你可以访问最新窗口的状态,另外通过调用window_set.delta(30)
,你可以访问 30 秒前的窗口状态。此外,你还可以通过为window_wrapper[key]
赋新值来更新最新窗口的状态。这种方法适用于滚动窗口,但不适用于跳跃窗口,跳跃窗口需要更新多个窗口的状态。
[Faust 文档:] 此时,在访问跳跃表中的数据时,我们总是访问给定时间戳的最新窗口,而且我们无法修改这种行为。
虽然 Faust 支持维护窗口状态、识别相关窗口并在已关闭的窗口上应用函数,但它并没有完全解决第三个功能,即更新所有相关窗口的状态。
Google Cloud 解决方案
我想简要讨论一下我在使用 Google Cloud Platform(GCP)时的负面体验。GCP 推荐使用 Google Pub/Sub 作为消息代理,Apache Beam 作为流处理库,Google Dataflow 作为执行工具,Google BigQuery 作为数据库。然而,当我尝试使用这个技术栈时,我遇到了许多问题,导致使用起来非常具有挑战性。
在 Python 中使用 Google Pub/Sub 证明是比较慢的(可以查看这个和这个),这让我放弃了它,转而使用 Kafka。Apache Beam 是一个文档齐全的库,但与 Kafka 一起使用时却遇到了自己的一些问题。直接运行器有漏洞,需要使用 Dataflow,且由于等待机器配置,导致了显著的时间延迟。此外,我还遇到了窗口触发延迟的问题,尽管我尝试过解决这个问题但都没有成功(可以查看这个GitHub 问题和这个Stack Overflow 贴文)。而且,由于多个组件的复杂集成,调试整个系统是一个巨大的挑战,这让我对日志的控制非常有限,也使得很难 pinpoint(定位)Pub/Sub、Beam、Dataflow 或 BigQuery 中问题的根本原因。总的来说,我在使用 Google Cloud Platform 的过程中,遇到了 Python 中 Google Pub/Sub 性能慢、使用 Apache Beam 与 Kafka 时的 bugs 以及调试这些互联系统的整体困难。
1 Kleppmann, Martin. 设计数据密集型应用:可靠、可扩展和可维护系统背后的核心理念。 “ O’Reilly Media, Inc.”, 2017。
精简数据管道:如何使用 PySpark 和 WhyLogs 进行高效的数据分析和验证
·发表于Towards Data Science ·阅读时间:9 分钟·2024 年 1 月 7 日
--
图片来自Evan Dennis提供的Unsplash
数据管道,由数据工程师或机器学习工程师创建,不仅仅是为了准备报告数据或训练模型。确保数据的质量同样至关重要。如果数据随着时间变化,你可能会得到意料之外的结果,这样是不好的。
为了避免这种情况,我们常常使用数据分析和数据验证技术。数据分析为我们提供关于数据集中不同列的统计信息。数据验证检查是否存在错误,将实际数据与预期数据进行比较。
一个很棒的工具是whylogs。它可以让你记录各种数据。记录后,你可以创建whylogs 配置文件。这些配置文件帮助你跟踪数据的变化,设置规则以确保数据的正确性,并以简便的方式展示汇总统计数据。
在这篇博客中,你将学习如何将 whylogs 与 PySpark 配合使用。我们将通过一个实践指南来讲解如何进行数据分析和验证。让我们开始吧!
目录
-
whylogs 的组件
-
环境设置
-
理解数据集
-
开始使用 PySpark
-
使用 whylogs 进行数据分析
-
使用 whylogs 进行数据验证
whylogs 的组件
让我们首先理解 whylogs 的重要特性。
-
数据记录:whylogs 的核心是其记录数据的功能。可以把它想象成记录数据特征的详细日记。它会记录数据的各个方面,比如有多少行、每列的值范围以及其他统计细节。
-
Whylogs 简介:一旦数据被记录,whylogs 会创建“简介”。这些简介就像是数据的快照,概括了数据的情况。它们包括诸如平均值、计数和分布等统计信息。这对于快速理解数据并追踪数据随时间的变化非常有用。
-
数据追踪:使用 whylogs,你可以追踪数据随时间的变化。这一点非常重要,因为数据通常会发生变化,上个月的情况可能今天就不再适用。追踪有助于你捕捉到这些变化,并理解它们的影响。
-
数据验证:Whylogs 允许你设置规则或约束,以确保数据符合预期。例如,如果你知道某一列应该只有正数,你可以为此设置规则。如果某些数据不符合你的规则,你将能够发现可能存在的问题。
-
数据可视化:通过可视化方式理解数据更加容易。Whylogs 可以创建图形和图表,帮助你更清楚地看到数据的动态,特别是对于那些不是数据专家的人来说,这使得数据更加易于访问。
-
集成:Whylogs 支持与多种工具、框架和语言的集成——如 Spark、Kafka、Pandas、MLFlow、GitHub actions、RAPIDS、Java、Docker、AWS S3 等。
这些就是我们需要了解的关于 whylogs 的所有信息。如果你想了解更多,我鼓励你查看文档。接下来,让我们开始为教程设置环境。
环境设置
我们将在本教程中使用 Jupyter notebook。为了让我们的代码在任何地方都能运行,我们将在 Docker 中使用 JupyterLab。这个设置会安装所有所需的库,并准备好示例数据。如果你是 Docker 新手并想学习如何设置 Docker,请查看这个链接。
[## GitHub - sarthak-sarbahi/whylogs-pyspark
通过在 GitHub 上创建账户,贡献于 sarthak-sarbahi/whylogs-pyspark 的开发。
从这里下载示例数据(CSV)。这些数据将用于数据简介和验证。创建一个data
文件夹在项目的根目录下,并将 CSV 文件保存到该文件夹中。接下来,在相同的根目录下创建一个Dockerfile
。
本教程的 Dockerfile(图片来自作者)
这个 Dockerfile 是一组创建特定环境的指令,用于本教程。我们来逐步解析它:
-
第一行
FROM quay.io/jupyter/pyspark-notebook
告诉 Docker 使用一个现有镜像作为起点。这个镜像是一个已经配置了 PySpark 的 Jupyter notebook。 -
RUN pip install whylogs whylogs[viz] whylogs[spark]
这一行是为了向环境中添加必要的库。它使用pip
安装whylogs
以及其附加功能:用于可视化的 (viz
) 和用于处理 Spark 的 (spark
)。 -
最后一行
COPY data/patient_data.csv /home/patient_data.csv
是将数据文件移入该环境。它将项目目录中data
文件夹下的 CSV 文件patient_data.csv
复制到 Docker 环境中的/home/
目录下。
到目前为止,你的项目目录应该是这样的。
在 VS Code 中的项目目录(图源:作者)
太棒了!现在,让我们构建一个 Docker 镜像。为此,请在终端中输入以下命令,确保你位于项目的根文件夹中。
docker build -t pyspark-whylogs .
这个命令创建了一个名为pyspark-whylogs
的 Docker 镜像。你可以在Docker Desktop应用的“镜像”标签中看到它。
构建的 Docker 镜像(图源:作者)
下一步:让我们运行这个镜像来启动 JupyterLab。请在终端中输入另一个命令。
docker run -p 8888:8888 pyspark-whylogs
这个命令从pyspark-whylogs
镜像启动一个容器。它确保你可以通过计算机的 8888 端口访问 JupyterLab。
运行这个命令后,你会在日志中看到一个类似于这样的 URL:http://127.0.0.1:8888/lab?token=your_token
。点击该链接以打开 JupyterLab Web 界面。
Docker 容器日志(图源:作者)
太棒了!一切已经为使用 whylogs 设置好了。现在,让我们了解一下我们将要处理的数据集。
理解数据集
我们将使用一个关于医院患者的数据集。该文件名为patient_data.csv
,包含 100k 行数据,列包括:
-
patient_id
:每个患者的唯一 ID。记住,数据集中可能会出现相同的患者 ID 多次。 -
patient_name
:患者的姓名。不同的患者可能有相同的名字。 -
height
:患者的身高,单位为厘米。每次就诊时,患者的身高都是相同的。 -
weight
:患者的体重,单位为千克。体重总是大于零。 -
visit_date
:患者就诊的日期,格式为YYYY-MM-DD
。
关于这个数据集的来源,别担心。它是由 ChatGPT 创建的。接下来,让我们开始编写一些代码。
开始使用 PySpark
首先,在 JupyterLab 中打开一个新的 notebook。记得在开始工作之前保存它。
[## whylogs-pyspark/whylogs_pyspark.ipynb at main · sarthak-sarbahi/whylogs-pyspark
通过在 GitHub 上创建账户,为 sarthak-sarbahi/whylogs-pyspark 项目的开发做出贡献。
我们将首先导入所需的库。
# Import libraries
from typing import Any
import pyspark
from pyspark.sql import SparkSession
import pyspark.sql.functions as F
from whylogs.api.pyspark.experimental import collect_column_profile_views
from whylogs.api.pyspark.experimental import collect_dataset_profile_view
from whylogs.core.metrics.condition_count_metric import Condition
from whylogs.core.relations import Predicate
from whylogs.core.schema import DeclarativeSchema
from whylogs.core.resolvers import STANDARD_RESOLVER
from whylogs.core.specialized_resolvers import ConditionCountMetricSpec
from whylogs.core.constraints.factories import condition_meets
from whylogs.core.constraints import ConstraintsBuilder
from whylogs.core.constraints.factories import no_missing_values
from whylogs.core.constraints.factories import greater_than_number
from whylogs.viz import NotebookProfileVisualizer
import pandas as pd
import datetime
然后,我们将设置一个 SparkSession。这让我们能够运行 PySpark 代码。
# Initialize a SparkSession
spark = SparkSession.builder.appName('whylogs').getOrCreate()
spark.conf.set("spark.sql.execution.arrow.pyspark.enabled","true")
之后,我们将通过读取 CSV 文件来创建一个 Spark 数据框。我们还会检查它的架构。
# Create a dataframe from CSV file
df = spark.read.option("header",True).option("inferSchema",True).csv("/home/patient_data.csv")
df.printSchema()
接下来,让我们先看一下数据。我们将查看数据框中的第一行。
# First row from dataframe
df.show(n=1, vertical=True)
既然我们已经查看了数据,现在是时候开始使用 whylogs 进行数据分析了。
使用 whylogs 进行数据分析
为了对数据进行分析,我们将使用两个函数。首先是 collect_column_profile_views
。这个函数为数据框中的每一列收集详细的分析配置。这些配置为我们提供统计信息,比如计数、分布等,具体取决于我们如何设置 whylogs。
# Profile the data with whylogs
df_profile = collect_column_profile_views(df)
print(df_profile)
数据集中的每一列都会在字典中获取一个 ColumnProfileView
对象。我们可以检查每列的各种指标,比如它们的均值。
whylogs 会查看每个数据点,并通过统计方式决定该数据点是否与最终计算相关。
例如,让我们看看 height
的平均值。
df_profile["height"].get_metric("distribution").mean.value
接下来,我们还将直接从数据框中计算均值以进行对比。
# Compare with mean from dataframe
df.select(F.mean(F.col("height"))).show()
然而,仅仅逐列进行数据分析并不总是足够的。因此,我们使用另一个函数,collect_dataset_profile_view
。这个函数对整个数据集进行分析,而不仅仅是单列。我们可以将其与 Pandas 结合,分析所有分析指标。
# Putting everything together
df_profile_view = collect_dataset_profile_view(input_df=df)
df_profile_view.to_pandas().head()
我们还可以将这个分析结果保存为 CSV 文件,以备后用。
# Persist profile as a file
df_profile_view.to_pandas().reset_index().to_csv("/home/jovyan/patint_profile.csv",header = True,index = False)
/home/jovyan
文件夹位于我们的 Docker 容器中,来自Jupyter 的 Docker 镜像堆栈(包含 Jupyter 应用程序的现成 Docker 镜像)。在这些 Docker 设置中,‘jovyan’ 是运行 Jupyter 的默认用户。/home/jovyan
文件夹是 Jupyter 笔记本通常启动的位置,也是您应将文件放置在其中以便在 Jupyter 中访问的地方。
就这样,我们使用 whylogs 对数据进行分析。接下来,我们将探索数据验证。
使用 whylogs 进行数据验证
对于我们的数据验证,我们将执行以下检查:
-
patient_id
:确保没有缺失值。 -
weight
:确保每个值都大于零。 -
visit_date
:检查日期是否采用YYYY-MM-DD
格式。
现在,让我们开始吧。whylogs 中的数据验证从数据分析开始。我们可以使用 collect_dataset_profile_view
函数来创建分析配置,就像我们之前看到的那样。
然而,这个函数通常会创建一个带有标准指标的分析配置,比如均值和计数。但如果我们需要检查列中的单个值,而不是对比其他约束条件,这时就可以使用条件计数指标。它就像是向我们的分析配置中添加了一个自定义的指标。
让我们为 visit_date
列创建一个检查,验证每一行。
def check_date_format(date_value: Any) -> bool:
date_format = '%Y-%m-%d'
try:
datetime.datetime.strptime(date_value, date_format)
return True
except ValueError:
return False
visit_date_condition = {"is_date_format": Condition(Predicate().is_(check_date_format))}
一旦我们有了条件,就将其添加到分析配置中。我们使用标准架构并添加自定义检查。
# Create condition count metric
schema = DeclarativeSchema(STANDARD_RESOLVER)
schema.add_resolver_spec(column_name="visit_date", metrics=[ConditionCountMetricSpec(visit_date_condition)])
然后,我们使用标准指标和我们为visit_date
列创建的自定义新指标重新创建了配置文件。
# Use the schema to pass to logger with collect_dataset_profile_view
# This creates profile with standard metrics as well as condition count metrics
df_profile_view_v2 = collect_dataset_profile_view(input_df=df, schema=schema)
在我们的配置文件准备好之后,我们可以为每一列设置验证检查。
builder = ConstraintsBuilder(dataset_profile_view=df_profile_view_v2)
builder.add_constraint(no_missing_values(column_name="patient_id"))
builder.add_constraint(condition_meets(column_name="visit_date", condition_name="is_date_format"))
builder.add_constraint(greater_than_number(column_name="weight",number=0))
constraints = builder.build()
constraints.generate_constraints_report()
我们还可以使用 whylogs 生成这些检查的报告。
# Visualize constraints report using Notebook Profile Visualizer
visualization = NotebookProfileVisualizer()
visualization.constraints_report(constraints, cell_height=300)
它将生成一个 HTML 报告,显示哪些检查通过,哪些失败。
whylogs 约束报告(图片来源:作者)
这是我们发现的内容:
-
patient_id
列没有缺失值。很好! -
一些
visit_date
值不符合YYYY-MM-DD
格式。 -
一些
weight
值为零。
让我们再次检查数据框中的这些发现。首先,我们用 PySpark 代码检查visit_date
格式。
# Validate visit_date column
df \
.withColumn("check_visit_date",F.to_date(F.col("visit_date"),"yyyy-MM-dd")) \
.withColumn("null_check",F.when(F.col("check_visit_date").isNull(),"null").otherwise("not_null")) \
.groupBy("null_check") \
.count() \
.show(truncate = False)
+----------+-----+
|null_check|count|
+----------+-----+
|not_null |98977|
|null |1023 |
+----------+-----+
它显示,100,000 行中有 1023 行不符合我们的日期格式。接下来是weight
列。
# Validate weight column
df \
.select("weight") \
.groupBy("weight") \
.count() \
.orderBy(F.col("weight")) \
.limit(1) \
.show(truncate = False)
+------+-----+
|weight|count|
+------+-----+
|0 |2039 |
+------+-----+
再次,我们的发现与 whylogs 一致。几乎有 2,000 行的权重为零。这也结束了我们的教程。你可以在这里找到本教程的笔记本。
结论
在本教程中,我们介绍了如何在 PySpark 中使用 whylogs。我们首先使用 Docker 准备了环境,然后对我们的数据集进行了数据分析和验证。记住,这只是开始。Whylogs 提供了更多功能,从机器学习中的数据变化(数据漂移)追踪,到实时流中的数据质量检查。
我真诚地希望这篇指南对你有所帮助。如果你有任何问题,请随时在下面的评论中提出。
参考文献
-
本教程的 GitHub 仓库:
github.com/sarthak-sarbahi/whylogs-pyspark/tree/main
-
Whylogs 文档:
docs.whylabs.ai/docs/whylogs-overview/
-
GitHub for whylogs:
github.com/whylabs/whylogs/tree/mainline
-
PySpark 中的数据分析:
github.com/whylabs/whylogs/blob/mainline/python/examples/integrations/Pyspark_Profiling.ipynb
-
Whylogs 约束在 PySpark 中的使用:
github.com/whylabs/whylogs/blob/mainline/python/examples/tutorials/Pyspark_and_Constraints.ipynb
精简房地产数据管理:使用 Indexify 进行高级数据提取和检索
使用 Indexify 进行文档查询的逐步指南
·发布于Towards Data Science ·13 分钟阅读·2024 年 8 月 31 日
--
图片由Tierra Mallorca提供,来源于Unsplash
简述:
-
传统的数据提取方法常常无法从非结构化内容中获取更深层次的洞察,尤其是在房地产行业中。
-
本文探讨了如何使用 Indexify,这一开源框架进行实时、多模态数据提取,更好地分析房地产文档。
-
您将学习如何设置 Indexify,包括服务器设置、提取图创建、文档摄取和数据查询,并了解如何创建自定义提取器。
-
实施 Indexify 可以增强房地产文档分析,带来更准确的洞察、更好的决策和更加高效的管理。
目录
· 简介
· Indexify 概述
∘ 提取器
∘ 协调器
· 教程前提
· 设置 Indexify 进行高级文档分析
∘ 安装与配置 Indexify
∘ 准备文档集合
· 使用 Indexify 摄取和处理文档
∘ 设置提取图
∘ 自定义提取器
∘ 上传文档
· 使用 Indexify 提出复杂问题
∘ 语义搜索与查询制定
∘…
精简你的提示语,降低 LLM 的成本和延迟
探索 5 种优化令牌使用的技巧,同时不牺牲准确性
·发布于Towards Data Science ·8 分钟阅读·2024 年 5 月 24 日
--
图片由作者使用 GPT-4o 生成
高成本和延迟是将 LLM 应用投入生产时的主要障碍之一,这与提示语的大小密切相关
大型语言模型的多功能性使得许多人认为它们可以作为几乎任何任务的“万能钥匙”。当你将 LLM 与包括 RAG 和 API 调用在内的工具结合,并给予它们详细指令时,它们常常能够在接近人类的水平上执行任务。
这种一体化方法的关键问题可能很快导致提示语的爆炸性增长,从而导致每次调用的成本和延迟变得不可行。
如果你使用 LLM 来优化像编程这样高价值的活动,保持低成本并不是首要任务——你可以等待半分钟来获取你通常需要花半小时编写的代码。然而,当在面向客户的应用中使用 LLM 时,尤其是在预期会有成千上万的聊天交互时,成本和延迟可能会决定你的解决方案能否成功。
本文是系列文章中的第一篇,在其中我将分享我为Resider.pl构建 LLM 驱动的房地产搜索助手‘Mieszko’的经验和见解…
启动新研究论文时优化工作流程
Python 代码用于创建生物医学科学研究论文的文件夹和 Word 文档——只需两个输入,立即完成所有操作
Rodrigo M Carrillo Larco, MD, PhD
·发布于 Towards Data Science ·6 分钟阅读·2024 年 12 月 8 日
--
图片来源:Maksym Kaharlytskyi 在 Unsplash
我是一名研究员,拥有超过七年的公共卫生和流行病学研究经验。每次我要开始撰写新的研究论文时,我都会为这个项目创建一个文件夹,在其中创建多个子文件夹来存放各个部分的工作,并为手稿创建带有特定标题的 Word 文档。迄今为止,我已发布超过 170 篇同行评审论文,估计这个过程我已经做了 200 多次。我认为是时候自动化这个过程了,今天我将与大家分享如何做到这一点!
在文章的最后,您会找到完整的代码。只需将代码复制并粘贴到您喜欢的 Python 环境中,点击运行即可。代码会生成一个名为 create_project_structure 的函数。这个函数将创建一个文件夹结构和 Word 文档,准备好让您直接开始撰写研究论文。
这个功能做什么?
这个函数将在指定的路径 (base_path
) 下生成一个文件夹,您可以自定义文件夹名称 (project_name
)。该函数还会创建两个 Word 文档,一个用于补充材料,另一个…
精简电子商务:利用实体解析进行产品匹配
Google 如何计算跨网站的产品价格
·发表于 Towards Data Science ·9 分钟阅读·2024 年 5 月 28 日
--
撰写者 Varun Joshi 与 Gauri Kamat
图片来自 Google 购物,查询词为“red polo ralph lauren”
随着电子商务在零售领域的主导地位不断扩大,跨平台和数据库之间准确匹配产品的挑战变得越来越复杂。在本文中,我们展示了产品匹配可以简单地看作是实体解析这一更广泛统计框架的一个实例。
产品匹配(PM)指的是确定两个不同的商品列表是否实际上指代同一产品的问题。在许多场景下,这一问题非常重要。例如,考虑以下几个用例:
-
随着在线市场的快速扩展,电子商务平台(如亚马逊)拥有成千上万的卖家提供他们的产品,而且新的卖家会定期加入平台。此外,这些卖家每天可能会向平台添加成千上万的新品1。然而,相同的产品可能已经由其他卖家在网站上出售。产品匹配是为了将这些不同的报价合并成一个列表,以便客户可以清晰地查看某个产品的不同报价。
-
在电子商务市场中,卖家也可能创建重复的商品列表,以在搜索页面上获得更多展示空间。换句话说,他们可以多次列出相同的产品(标题、描述等略有不同),以增加客户看到其产品的概率。为了改善客户体验,需要进行产品匹配,以检测和移除这些重复的商品列表。
-
另一个重要的应用场景是竞争者分析。为了设定有竞争力的价格并做出库存决策,电子商务公司需要了解竞争对手提供的同款产品的价格。
-
最后,价格比较服务,例如 Google 购物2,需要产品匹配来确定同一产品在不同平台上的价格。
本文中,我们展示了实体解析(ER)框架如何帮助我们解决 PM 问题。具体来说,我们描述了 ER 中广泛使用的一个框架,并展示了它在一个合成的 PM 数据集上的应用。我们首先提供与 ER 相关的背景信息。
什么是实体解析?
实体解析(ER)是一种识别重复实体的技术,通常适用于同一数据源内或跨数据源的情况。在同一数据库内的 ER 通常称为去重,而跨多个数据库的 ER 则称为记录链接。当有唯一标识符(如社会安全号码)时,ER 是一个相对简单的任务。然而,由于数据隐私的原因,这些标识符通常不可用。在这些情况下,ER 变得更加复杂。
为什么实体解析很重要?ER 可以帮助通过额外来源的数据增强现有数据库。这使得用户能够进行新的分析,而无需增加更多数据收集的成本。ER 在多个领域都有应用,包括电子商务、人权研究和医疗保健。一个近期应用涉及通过将 ER 应用于回顾性死亡率调查,来统计萨尔瓦多内战中的伤亡人数。另一个有趣的应用是去重美国专利商标局维护的专利数据库中的发明人姓名。
确定性与概率性实体解析(ER)
确定性实体关系方法依赖于所有属性的精确一致性。举例来说,假设我们有两个文件 A 和 B。假设我们正在比较文件 A 中的记录 a 和文件 B 中的记录 b。进一步假设,比较是基于两个属性:产品类型(例如,服装、电子产品)和制造年份。如果产品类型ᵃ = 产品类型ᵇ 且年份ᵃ = 年份ᵇ,则确定性规则会声明(a, b)为链接。如果所有属性都是类别属性,这种方法是可行的。但如果我们有像产品名称这样的文本属性,那么确定性链接可能会产生错误。例如,如果名称ᵃ = “索尼电视 4”且名称ᵇ = “索尼电视 4”,即使这两个名称仅相差一个空格,(a,b)也会被声明为非链接。
我们接下来需要的是能够考虑部分一致性水平的东西。在这里,可以使用概率性实体关系(probabilistic ER)。在概率性实体关系中,每一对记录(a,b)都被分配一个成为链接的概率,这个概率基于(1)有多少属性一致;以及(2)这些属性一致的程度。例如,如果产品类型ᵃ = 产品类型ᵇ,年份ᵃ = 年份ᵇ,且名称ᵃ和名称ᵇ非常相近,那么(a,b)将被分配一个较高的成为链接的概率。如果产品类型ᵃ = 产品类型ᵇ,年份ᵃ = 年份ᵇ,但名称ᵃ和名称ᵇ差异巨大(例如,“AirPods”和“索尼电视 4”),那么这个概率就会显著降低。对于文本属性,概率性实体关系依赖于字符串距离度量,例如 Jaro-Winkler 距离和 Levenshtein 距离。
Fellegi-Sunter 模型
Fellegi-Sunter 模型3提供了一个概率框架,使分析人员能够基于记录属性的相似性量化匹配的可能性。该模型通过计算来自两个文件的每对记录的匹配权重来运行。这个权重反映了它们各自属性的一致程度。对于给定的记录对,匹配权重为:
记录对的匹配权重
其中,mᵢ是两个记录在属性 i 上一致的概率,假设它们是匹配的; uᵢ是两个记录在属性 i 上一致的概率,假设它们是非匹配的;而 lambda 是匹配的先验概率,即在没有关于记录对的其他信息时匹配的概率。m 概率通常反映了用于链接的变量质量,而 u 概率则反映了非匹配记录对之间的偶然一致性。
匹配权重被转换为两个记录之间的匹配概率。
匹配概率
最后,将匹配概率与选定的阈值进行比较,以决定记录对是否匹配、非匹配或需要进一步人工审查。
使用合成产品数据的示例
数据生成
我们生成数据以反映一个现实的产品匹配场景。具体来说,我们生成了文件 A,包含 79 条记录,文件 B,包含 192 条记录。两者之间有 59 条重叠记录。两个文件都包含四个链接变量,即产品名称、产品类型、品牌和价格。例如,文件 A 中表示 Apple AirPods 的记录,产品名称为“Apple AirPods”,产品类型为“耳塞”,记录的品牌为“Apple”,产品价格为 200 美元。产品名称、类型和品牌是字符串类型的变量,而价格是连续数值型变量。我们还在每个链接变量中引入了错误。在字符串类型字段中,我们引入了删除错误;例如,Apple Series 6 手表可能在文件 A 中记录为“Apple Watch Series 6”,而在文件 B 中记录为“Apple Watch 6”。我们还在字符串字段中引入了大小写变更错误;例如,同一产品可能在文件 A 中记录为“apple watch series 6”,而在文件 B 中记录为“Apple Watch 6”。价格变量的连续性可能自动引入错误。例如,某个产品在一个文件中的价格为 55 美元,而在另一个文件中为 55.2 美元。
对于合成数据生成,我们使用了免费版本的 ChatGPT(即 GPT 3.5)4。以下三个提示用于数据生成:
提示 1:生成带有链接的数据集
Generate a synthetic dataset which links 59 distinct products from two different sources.
The dataset should have the following columns: Title_A, Product_Type_A, Brand_A, Price_A, Title_B, Product_Type_B, Brand_B, Price_B.
Each row of the dataset refers to the same product but the values of the corresponding columns from Dataset A and Dataset B can be slightly different. There can be typos or missing value in each column.
As an example, check out the following couple of rows:
Title_A | Product_Type_A | Brand_A | Price_A | Title_B | Product_Type_B | Brand_B | Price_B
Levis Men 505 Regular | Jeans | Levis | 55 | Levs Men 505 | Jeans | Levis | 56
Toshiba C350 55 in 4k | Smart TV | Toshiba | 350 | Toshiba C350 4k Fire TV | Smart TV | Toshiba Inc | 370
Nike Air Max 90 | Sneakers | Nike | 120 | Nike Air Max 90 | Shoes | Nikes | 120
Sony WH-1000XM4 | Headphones | Sony | 275 | Sony WH-1000XM4 | | Sony | 275 |
Make sure that |Price_A - Price_B| *100/Price_A <= 10
Output the dataset as a table with 59 rows which can be exported to Excel
上述提示生成了带有链接的数据集。可以修改行数以生成具有不同数量链接的数据集。
为了为每个单独的数据集(数据集 A 或数据集 B)生成更多记录,使用了以下两个提示。
提示 2:为数据集 A 生成更多记录
Generate 20 more distinct products for the above dataset. But this time, I only need the information about dataset A. The dataset should have the following columns: Title_A, Product_Type_A, Brand_A, Price_A
提示 3:为数据集 B 生成更多记录
Now generate 60 more distinct products for the above dataset. But this time, I only need the information about dataset B. The dataset should have the following columns: Title_B, Product_Type_B, Brand_B, Price_B. Don't just get me electronic products. Instead, try to get a variety of different product types e.g., clothing, furniture, auto, home improvement, household essentials, etc.
记录链接
我们的目标是使用 Fellegi-Sunter (FS) 模型识别文件 A 和 B 之间的重叠记录。我们通过 Python 中的 splink 包实现 FS 模型 5。
为了比较产品标题、产品类型和品牌,我们使用了 splink 包中提供的默认 name 比较函数。具体来说,该比较函数有以下 4 个比较级别:
-
精确匹配
-
Damerau-Levenshtein 距离 <= 1
-
Jaro Winkler 相似度 >= 0.9
-
Jaro Winkler 相似度 >= 0.8
如果一对产品不属于任何 4 个级别,则为该对记录分配一个默认的 其他 级别。
splink 包没有比较数值列的功能。因此,对于价格比较,我们首先将价格转换为分类变量,通过将价格划分到以下几个区间:[<$100, $100–200, $200–300, $300–400, $400–500, $500–600, $600–700, $700–800, $800–900, $900–1000, >=$1000]。然后,我们检查一对记录的价格是否落在相同的区间内。换句话说,我们使用 精确匹配 比较级别。
所有比较都可以通过 splink 包中的设置字典来指定。
FS 模型的参数使用期望最大化算法进行估计。在splink中,提供了内置的函数来实现这一点。
为了评估 FS 模型的表现,我们记录了已连接记录的数量、精确度、召回率和 F1 得分。精确度定义为已连接记录中真实链接的比例。召回率定义为真实链接中正确识别的比例。F1 得分等于 2精确度召回率 /(精确度 + 召回率)。splink提供了一个函数来生成这些度量,结果如下所示:
用于训练和评估此模型的完整代码可以在这里找到:github.com/vjoshi345/product-matching-article/blob/main/train_synthetic_fellegi_sunter.py
结果
我们在两个数据集中的所有可能产品对上运行了 FS 模型。具体来说,共有 15,168 个产品对(79 * 192)。splink包有一个功能,可以自动生成不同匹配概率阈值下的预测(即匹配链接)。下面我们展示了匹配概率=0.913 时的混淆矩阵(这是我们获得最高 F1 得分的阈值)。
PM 预测的混淆矩阵
已连接记录的总数 = 82
精确度 = 58 / 82 = 0.707
召回率 = 58 / 59 = 0.983
F1 = (2 * 0.707 * 0.983) / (0.707 + 0.983) = 0.823
结论
本文的目的是展示产品匹配是更通用的实体解析问题的一个具体实例。我们通过利用 ER 框架中的一个流行模型来解决产品匹配问题来证明这一点。由于我们希望这是一篇入门文章,我们创建了一个相对简单的合成数据集。在实际场景中,数据将更加复杂,包含几十种不同的变量,例如产品描述、颜色、尺寸等。为了准确匹配,我们需要更先进的 NLP 技术,超越文本距离度量。例如,我们可以利用来自 Transformer 模型的嵌入表示来语义上匹配产品。这可以帮助我们匹配两个语法上不同的产品描述,例如,一个产品的类型是Jeans,另一个是Denims。
此外,现实世界数据集中的产品数量将达到数亿,可能包含数十万个链接。这类数据集需要更高效的方法和计算资源来有效地进行产品匹配。
参考文献
3 I. Fellegi 和 A.B. Sunter (1969)。记录链接的理论。美国统计学会期刊
精简巨头
LLM 时代模型压缩的演变
·发表于Towards Data Science ·阅读时间 20 分钟·2024 年 2 月 29 日
--
图片来源:作者,使用 DALL-E 3 生成。
2017 年,transformers的出现引发了人工智能的里程碑式进展,首先是大型语言模型(LLMs)在自然语言处理(NLP)领域的惊人突破,随后迅速推动了计算机视觉和机器人等领域的进步,参见计算机视觉与机器人。NLP 和计算机视觉问题的统一架构加速了联合视觉-语言表示空间的学习,这为 2021 年围绕对比语言-图像预训练的视觉-语言建模(CLIP)的开创性成果铺平了道路,并催生了大型多模态模型(LMMs)的诞生。
这一大型模型的曙光时代展现了令人敬畏的能力,并在朝着人工通用智能(AGI)迈进的过程中取得了几个重要进展,但这些模型的巨大规模使得它们的部署变得困难。与许多变革性技术一样,LLMs 的迷人能力最初只有那些拥有前沿技术资源的人才能使用。尽管私人研究仍在使用数百亿参数的 LMMs 推动性能的极限,但开源研究已经建立了一个追赶这些水印的模式,使用的是规模更小的模型。
图片来源:作者。随着时间的推移,开源 LLMs 正在缩小与规模更小模型的性能差距。
然而,尽管其效能不断增强,最小的 LLM 仍然无法在消费级 GPU 上进行推理,更别提进行训练,除非应用了模型压缩技术。幸运的是,这些模型令人垂涎的能力促使研究人员找到了有效的方法,将它们压缩到更小的空间。得益于他们的努力,我们现在可以轻松地在消费级硬件上部署 LLM,并根据我们的需求进行微调,而无需依赖企业巨头的资源。本系列文章全面回顾了四种模型压缩技术,这些技术使得在资源受限的环境中实现 LLM 推理和微调成为可能,下面列出了这些技术:
大型语言模型的模型压缩技术
-
剪枝 — 根据网络参数在模型中的重要性,单独(无结构)或按组(结构化)移除参数,以降低模型复杂度,同时保持准确性。
-
量化 — 离散化和/或降低权重数值空间的分辨率,以节省空间并简化计算。
-
知识蒸馏 — 训练小型模型以模仿大型专家模型学习的功能,这些小型模型可以在没有标注数据的情况下进行训练,并且在与在原任务上训练的小型模型相比时,表现更好。
-
参数高效微调 — 减少微调模型以实现特定任务行为所需的可训练参数数量。
这些技术中的每一种都已被证明可以显著提高大模型的效率,同时对性能的影响相对无害,将大型预训练语言模型(PLM)的惊人能力带到现实世界中,使得任何人都可以使用它们。在这里,我们详细探讨每一种技术,以便我们能够在自己的工作中加以应用。像大型语言模型一样,这些话题只能在一定程度上进行压缩,否则会导致知识的重大损失。因此,我们将把这个讨论分成一系列文章,以便为每种技术提供足够的空间,充分回顾这些丰富的研究成果,这些成果为我们提供了强大的渠道,将火种带给人类。
在本文中,我们从列表中最古老的技术开始,它几乎与反向传播训练的神经网络一样古老,它就是:剪枝。首先,通过快速回顾剪枝的历史,我们将学习到无结构和结构化剪枝技术之间的区别,以及它们的比较优缺点。掌握了这些前置知识后,我们将回顾这些方法在当今大型语言模型(LLMs)中的应用,并给出结语。
《精简巨人》系列的后续章节将深入探讨每种剩余的压缩技术:量化、知识蒸馏和参数高效微调,阐明每种技术的清晰而全面的理解,使我们能够以全副武装的姿态进入 LLM 开发的游戏。
剪枝
作者使用 DALL-E 3 创作的图像。
为实际应用而精炼神经网络的探索可以追溯到该领域的基础时期。当Rumelhart, Hinton, 和 Williams在 1986 年首次展示如何使用反向传播算法成功训练能够学习复杂非线性表示的多层神经网络时,这些模型的巨大潜力显现出来。然而,1980 年代的计算能力限制了这些模型的实际应用以及它们能够解决问题的复杂性,这与我们今天在部署 LLM 时面临的挑战相似。尽管模型的规模和所做的考虑因素非常不同,但早期在网络最小化方面的发现为数十年后模型压缩领域的重大突破铺平了道路。在本节中,我们将简要回顾剪枝研究的历史和动机,发现非结构化方法与结构化方法的相对优缺点,并为探索它们在现代 LLM 时代的应用做好准备。
网络剪枝最初的动机是通过将不重要的权重冻结为零,从而追求更好的模型泛化,这在理论上有点类似于线性回归中的 L1/Lasso 和 L2/Ridge 正则化,尽管不同之处在于,剪枝是在训练后根据重要性标准选择并硬性设置为零(修剪),而不是通过训练中的损失函数将权重数学上推向零(有经验的读者会知道,正则化也可以通过权重衰减在神经网络训练中实现)。
正则化和剪枝(可以视为正则化的一种形式)背后的共同动机是理论和经验的证据表明,神经网络在过度参数化的情况下最有效地学习,因为此时损失函数全局最小值的高维流形和更大的探索空间使得有效的子网络更容易被初始化(参见“彩票票据假设”)。然而,这种过度参数化反过来又导致了对训练数据的过拟合,最终使得网络中有许多冗余或不活跃的权重。尽管当时关于过度参数化神经网络“非理性有效性”的理论机制研究较少,但 1980 年代的研究人员正确地假设,在训练后应该可以移除网络中大量的权重,而不会显著影响任务性能,而且通过进行迭代的剪枝和微调剩余模型权重,应该能获得更好的泛化能力,从而增强模型在未见数据上表现良好的能力。
非结构化剪枝
为了选择需要移除的参数,必须衡量它们对成本函数的影响,或者称为“显著性”。尽管最早的网络最小化工作假设参数的大小应作为衡量显著性的合适标准,但 LeCun 等人在 1989 年提出的“最优大脑损伤”(OBD)中,迈出了重要的一步,他们提出使用成本函数对参数的二阶导数信息作为显著性的理论可行度量,从而能够直接识别出那些能够在误差增加最小的情况下被移除的参数。
写于模型仅包含 2,600 个参数的完全连接神经网络时代,《OBD》的作者们当时更关注的是通过减少模型复杂性来提高模型对未见数据的泛化能力,而不是像今天在面对数十亿参数的庞大模型时,主要关注的是去除权重以提高计算效率。即便是对这样一个微小的模型进行操作,计算二阶导数信息(Hessian 矩阵)仍然是非常昂贵的,这要求作者做出三个方便的数学假设:1)假设模型当前已训练至最优,即每个权重的损失梯度目前为零,并且梯度的斜率在两个方向上均为正,这使得泰勒展开中的一阶项为零,并且意味着修剪任何参数引起的损失变化是正的;2)假设 Hessian 矩阵是对角矩阵,即每个参数删除引起的损失变化是独立的,因此损失增量可以在权重子集上求和,从而计算它们共同删除所导致的总损失变化;3)假设损失函数几乎是二次的,即可以忽略泰勒展开中的高阶项。泰勒展开。
OBD的结果优于基于幅度的剪枝(左)。OBD 显著性估计的准确性(右)。
尽管有这些天真的假设条件,它们理论上合理的封闭形式显著性度量方法证明在准确识别网络中最不重要的权重方面优于基于幅度的剪枝,能够在更高的压缩率下保持更多的准确性。然而,基于幅度的剪枝方法的有效性和深刻的简洁性使其成为未来模型压缩研究中许多工作的首选,尤其是当网络规模迅速增长,Hessian 矩阵变得指数级增长时。尽管如此,使用理论上合理的显著性度量来更准确地估计显著性,从而实现更激进的剪枝的成功示范,提供了未来模型压缩研究的灵感食谱,尽管这些种子要经过一段时间才能结出果实。
来自OBD的结果表明,反复进行剪枝和微调可以在参数数量减少到原来的一半以下时,仍能保持原有的性能水平。这在当今大模型的背景下意义重大,但他们更关注的是提升模型的泛化能力。
四年后的 1993 年,Hassibi 等人提出的最优脑外科医生(OBS)在 OBD 的基础上扩展了这一概念,并通过摒弃 OBD 中的对角性假设,改为考虑 Hessian 矩阵中的交叉项,提升了在不增加误差的情况下可能的压缩水平。这使得他们能够根据去除给定参数来确定剩余权重的最优更新,从而同时进行剪枝和优化模型,避免了重新训练的需求。然而,这也意味着更复杂的数学,因而 OBS 最初对于 21 世纪研究人员在处理更大规模网络时的实用性有限。尽管如此,像 OBD 一样,OBS 最终也会在未来的里程碑中复兴其遗产,正如我们稍后将看到的那样。
OBD 和 OBS 中的修剪方法是无结构修剪的典型例子,其中根据权重的显著性度量,对每个权重进行单独修剪。无结构修剪技术的现代典范是Han et al. 2015,他们通过一次或多次基于幅度的权重修剪和微调,分别将早期的工作马卷积神经网络(CNN)AlexNet和VGG-16的大小分别减少了 9 倍和 13 倍,且没有损失精度。遗憾的是,他们的方法需要对网络层进行灵敏度分析,以确定每个单独层的最佳修剪率,并且最好在至少重新训练一次后使用,这意味着它不适合用于极大型网络。尽管如此,看到他们使用无结构方法所能实现的修剪程度仍然令人印象深刻,尤其是他们使用的是基于幅度的修剪。与任何无结构方法一样,减少的内存占用只能通过使用稀疏矩阵存储技术来实现,这样就可以避免将零化的参数存储在密集矩阵中。尽管他们在研究中没有使用,但作者在相关工作部分提到,哈希技巧(如 2015 年HashedNets论文中展示的)与无结构修剪是互补的,因为增加稀疏性减少了网络中唯一权重的数量,从而降低了哈希碰撞的概率,进而减少存储需求并提高哈希函数的权重检索效率。
Han et al. 2015的结果展示了当时 CNN 中无结构修剪的强大能力。注意到“免费午餐”压缩了 40-50%的参数,而没有损失精度,也不需要重新训练。
尽管无结构剪枝通过减少模型复杂度实现了预期的正则化效果,进而通过使用稀疏矩阵存储方法大幅缩小了内存占用,但这种剪枝方式在计算效率上的提升并不容易直接获得。单纯地将个别权重置零而不考虑网络架构,会导致生成的矩阵稀疏性不规则,在使用标准硬件进行密集矩阵计算时,无法实现计算效率的提升。只有专门设计用于利用矩阵运算稀疏性的硬件,才能解锁无结构剪枝所带来的计算效率提升。幸运的是,具有这些能力的消费级硬件正在变得越来越普及,使得用户能够从无结构剪枝所生成的稀疏矩阵中实现性能提升。然而,即便是这些专用硬件,也必须对每个矩阵行中应被剪枝的权重数量施加稀疏性比例的预期,以便允许算法利用结果中的稀疏性,这被称为半结构化剪枝,而强制这一约束已被证明比纯粹的无结构剪枝更容易导致性能下降。
结构化剪枝
我们已经看到,无结构剪枝是一种已被证明能够改善模型泛化、减少内存需求并在专用硬件上提供效率提升的成熟正则化技术。然而,结构化剪枝提供的计算效率提升更为显著,这种方法通过从网络中移除整个结构组件(如滤波器、层),而非单个权重,从而减少了网络的复杂度,这种复杂度的降低与硬件上计算的执行方式相一致,使得计算效率的提升可以在无需专用硬件的情况下轻松实现。
2016 年,Li 等人发表的论文《高效卷积神经网络的滤波器剪枝》为普及结构化剪枝的概念做出了重要贡献。正如标题所示,作者通过剪枝卷积神经网络(CNN)中的滤波器及其相关特征图来大幅提升计算效率,因为围绕这些滤波器的计算可以通过物理去除选择的内核直接排除,从而减少矩阵的大小及其乘法运算,无需担心利用稀疏性。作者使用滤波器权重的简单和(L1 范数)来进行基于大小的剪枝,展示了他们的方法可以分别将 VGG-16 和ResNet-110的推理成本降低 34%和 38%,且没有显著降低准确率。
Li et al. 2016展示了从 CNN 中剪除卷积滤波器的效果。
他们的研究还揭示了一些关于卷积网络工作原理的有趣见解,通过比较各个 CNN 层对剪枝的敏感性,发现网络最初或接近网络深度一半的层可以被大幅度剪枝,几乎不会影响模型性能,而在网络深度约 1/4 处的层则对剪枝非常敏感,剪枝后即使重新训练也难以恢复模型性能。下图显示的结果表明,最对剪枝敏感的层是那些包含大量具有较大绝对和的滤波器的层,支持了“幅度”作为显著性度量的理论,因为这些层显然对网络更为重要,剪去这些层会对模型性能造成明显的负面影响,且难以恢复。
Li et al. 2016的研究结果揭示了 CNN 层对滤波器剪枝的敏感性存在显著差异。
最重要的是,Li et al.的结果显示,CNN 中的许多层即使剪去高达 90%的滤波器,也不会损害(在某些情况下甚至能改善)模型性能。此外,他们还发现,当剪去那些不敏感的层的滤波器时,不需要逐层进行迭代重训练,单次剪枝和重训练(仅用原训练时间的 1/4)就足以在剪去大量网络部分后恢复模型性能。这对提高效率是个好消息,因为多轮重训练成本高昂,而之前的研究报告称,剪枝后的模型需要最多 3 倍的原始训练时间。下图显示了 Li et al.的总体结果,揭示了在研究的 CNN 中,可以减少 15%到 40%的浮点运算量(FLOPs),而不损害性能,实际上在许多情况下还带来了提升,明确树立了训练后剪枝模型的重要性。
Li et al. 2016将他们的选择性剪枝配置与基准 CNN 进行比较,评估了 CIFAR-10(前三个模型)和 ImageNet(ResNet-34 部分)。
尽管这项研究显然是出于效率考虑,但我们从数十年的证据中知道,简化模型复杂度与提高泛化能力之间的关联性,这些网络在未见过的数据上表现得更好,这也是最初推动剪枝研究的根本原因。然而,这种剪枝方法需要对网络层进行敏感性分析,以便正确执行,这需要额外的努力和计算。此外,正如 LeCun 及其同事在 1989 年正确指出的那样:尽管基于幅度的剪枝是一种经过时间考验的策略,但我们应该期望一个理论上有依据的显著性度量能够产生更优的剪枝策略,但考虑到现代神经网络的规模,计算 Hessian 矩阵以进行 OBD 方法中所需的二阶泰勒展开将变得过于繁重。幸运的是,最终出现了一个折中的方法。
在 2016 年末,仅比 Li 等人晚几个月,Molchanov 及其在 Nvidia 的同事们重新研究了使用泰勒展开来量化卷积神经网络(CNN)中滤波器结构剪枝的显著性。与 OBD 方法不同,他们避免了计算二阶项的复杂过程,而是通过考虑一阶泰勒展开项的方差而非均值来提取有用的显著性度量。这项研究提供了几种显著性度量与通过全面计算去除每个滤波器对精调后的 VGG-16 模型损失变化所得到的“oracle”排名之间的实证对比。从下面的结果可以看出,所提出的泰勒展开显著性度量与 oracle 排名最为一致,其次是计算量更大的 OBD 方法,性能结果也表明这些方法在保持准确性方面表现最佳,当我们根据 GFLOPs 绘制结果时,所提出的泰勒展开方法在优势上更为明显。有趣的是,他们研究中引入的随机滤波器剪枝表现出令人惊讶的效果,相较于最小权重(基于幅度的)剪枝,挑战了权重幅度是显著性可靠度量这一观念,至少对于他们研究的 CNN 架构而言。
Molchanov 等人 2016的结果表明,使用一阶泰勒展开可以有效地衡量滤波器显著性,呈现出与 oracle 排名的最高相关性,并且能够最好地保持准确性。
剪枝 LLM
图片由作者使用 DALL-E 3 生成。
在大规模语言模型(LLMs)被广泛采用之后,研究人员自然开始研究在这些架构上使用剪枝技术。无论是无结构剪枝还是有结构剪枝,都可以成功地应用于 LLMs,显著减少模型大小,同时性能几乎没有下降。然而,正如人们所预料的,这些模型的庞大规模需要特别的考虑,因为计算包含数十亿甚至数百亿权重的模型的显著性度量非常昂贵,而剪枝后重新训练以恢复模型性能的成本则高得令人难以承受。因此,现如今有了新的动机,要求在尽可能少的重新训练下执行剪枝,并在用于剪枝的显著性度量中强制要求简洁性。
与之前的剪枝研究阶段一致,显然 LLMs 可以通过无结构方法比有结构方法更为激进地进行剪枝,但同样,后者的效率提升更加直接易得。对于那些可以更好地访问专业资源的实践者来说,利用无结构剪枝所提供的稀疏矩阵和巨大的压缩率可能是正确的选择,但对于许多人来说,尽管有结构剪枝提供的压缩率较为温和,但它在一般硬件上提供的效率提升会更具吸引力。在本节中,我们将回顾今天 LLMs 领域的这两种方法,以便我们根据个人的具体情况做出最佳选择。
LLMs 的无结构剪枝
在 2023 年初,SparseGPT是首个研究对具有数十亿参数的 GPT 模型进行无结构剪枝的工作,提出了一种高效的方法,利用一种新型的近似稀疏回归求解器来确定这种规模模型中的可剪枝权重,且能够在数小时内完成计算,并且展示了当时最大的开源模型(≤175B)可以在不进行任何重新训练的情况下,单次剪枝至 50%到 60%的稀疏度,且准确度几乎没有损失,这一结果显著超过了基于幅度的剪枝方法在单次操作中的效果。他们的方法采用了迭代式的 OBS 视角,发现同样的数学结果可以分解为一系列更加高效计算的操作。然而,由于他们的方法仍然属于无结构剪枝,因此需要专用硬件来实现该技术的效率提升,而强制要求的 2:4 或 4:8 半结构稀疏模式则会导致与纯无结构剪枝相比的性能下降。
来自SparseGPT的结果显示,相较于基于幅度的剪枝(左图),该方法具有明显优势,并展示了强制稀疏模式以便进行硬件优化(右图)所带来的负面影响。
在 2023 年中期,Wanda的作者提出了一个假设,解释了为什么量化在 LLM 时代比剪枝受到更多的研究关注,而以前这两种压缩技术曾经同样受欢迎。他们将这一现象归因于,在 SparseGPT 出现之前,所有的剪枝方法都需要在某个时刻重新训练 LLM,这使得没有足够资源的人难以负担,成为研究和实际应用的重大障碍。尽管 SparseGPT 表明一次性剪枝是可能的,但他们的迭代 OBS 方法仍然计算密集。因此,Wanda 选择了一种简单的基于大小的无结构剪枝方法,通过将权重大小与其相关输入激活的范数相乘,创建了一个更具描述性和整体性的基于大小的重要性度量。下面的比较图表展示了这些无结构方法的重要性公式和复杂度。
来自Sun et al. 2023的表格比较了大规模语言模型(LLMs)的无结构剪枝方法及其复杂度。
Wanda 的方法也能生成无需任何重新训练即可使用的剪枝模型,但作为一种无结构方法,依然需要特殊硬件来实现效率提升。尽管如此,对于那些具备条件进行无结构剪枝的用户来说,Wanda 的方法与 SparseGPT 的结果相当或更好,同时将复杂度降低到模型隐藏层维度的一个因子,确立了它作为 LLM 压缩的有力选择。
来自Sun et al. 2023的表格显示,结构化剪枝在复杂度较低的情况下,与 SparseGPT 具有竞争力的表现。
LLM 的结构化剪枝
与 Wanda 同时,新加坡国立大学的研究人员提出了一种名为LLM-Pruner的结构化剪枝方法。在他们的研究中,他们发现有必要设定 20%的剪枝率,因为更激进的剪枝会导致性能显著下降。此外,尽管在剪枝后需要重新训练权重以恢复模型性能,但他们能够使用低秩适应(LoRA)在 50k 训练样本上仅用 3 小时就完成这一过程。尽管使用 LoRA 进行微调的高效性令人宽慰,但他们的方法仍然需要在剪枝前生成全模型的梯度以衡量参数的重要性,因此,虽然资源受限的用户可以享受剪枝后的模型,但自己执行该操作可能并不可行。
2023 年稍晚时,LoRAPrune通过使用 LoRA 训练的梯度和权重来确定更大网络中参数的重要性,并对网络及相应的 LoRA 权重进行迭代剪枝,显著提高了 LLM 的结构化剪枝效果。这种方法能够在单个 80GB A100 GPU 上剪枝 LLaMA-65B 模型,因为它依赖于高效低秩参数空间的梯度,而非完整模型。由于在整个过程中 LLM 的权重保持冻结状态,因此可以将其量化为 8 位,以节省内存,并对结果的影响最小。
Zhang et al. 2023的有用图示展示了 LoRAPrune 中使用的结构化剪枝方法。
尽管他们面临着 LLM 对更激进的结构化剪枝水平的敏感性,LoRAPrune 的作者通过大量实验展示了他们的方法,在使用远少于传统剪枝资源的情况下,产生了性能更优的修剪模型。
来自LoRAPrune的结果表明,与之前的方法相比,微调后显示出明显的优势。
在 2023 年 10 月,微软的研究人员提出了LoRAShear,这是一种结构化剪枝方法,通过对 LLM 进行依赖图分析和通过 LoRA 半空间投影梯度(LHSPG)进行渐进式结构化剪枝,“将存储在相对冗余结构中的知识转移到重要结构中,以更好地保留预训练 LLM 的知识。”此外,他们超越了以往仅通过指令微调来恢复知识的趋势,而是首先基于结果的性能分布自适应地从预训练数据集中创建一个子集,以恢复在剪枝过程中丢失的通用预训练知识,然后进行“常规的指令微调,以恢复修剪后的 LLM 的领域特定专业知识和指令能力。”通过他们更为复杂的方法,他们在 20%的剪枝率下仅表现出 1%的性能下降,并在 50%的剪枝率下保持了前所未有的 82%的原始性能。
来自 LoRAShear 的结果显示了优越的性能,尽管其剪枝算法更加复杂。
然后,在 2024 年初,名为Bonsai的方法展示了一种优越的 LLM 结构化剪枝方法,该方法仅使用前向传递信息,显著减少了剪枝过程的计算需求,因为不需要梯度计算,从而使那些最需要剪枝模型的用户能够在资源受限的环境中生成它们。通过这种高效的方法,他们能够在仅限于指令调优的条件下,接近地匹配 LoRAShear 的性能,尽管看起来 LoRAShear 所做的额外考虑确实有助于知识恢复,但两项研究中使用的评估数据集的不同分布不幸使得无法进行清晰的比较。有趣的是,Bonsai 论文中没有提及 LoRAShear,推测原因是前者的复杂性使得与后者所考察的更简单方法的比较变得不清晰,但我们只能进行推测。尽管如此,Bonsai 通过专注于简单性和效率,做出了向民主化 LLM 及其剪枝迈出的强大而宝贵的一步,能够仅使用运行给定模型推理所需的 GPU 内存量来执行剪枝操作,并且实现了迄今为止发表的最易于访问的结构化 LLM 剪枝方法,取得了令人印象深刻的结果。
Dery 等人的结果表明,Bonsai 在性能上优于先前的结构化剪枝方法。
结论
作者使用 DALL-E 3 制作的图片。
在本文中,我们回顾了网络剪枝的历史,从 1980 年代末期的无结构剪枝技术的黎明到当前 LLM 应用的趋势。我们看到,剪枝大致可以分为无结构剪枝和结构化剪枝,这取决于权重是单独考虑还是按组考虑,而后者虽然只能在较低压缩率下使用,但能够直接减轻计算负担。我们看到,在无结构剪枝的环境中,通过使用特殊的存储技术和硬件可以实现效率提升,但必须遵守一个额外的“半结构化”条件,才能使硬件加速生效,这在性能上会带来相较纯无结构剪枝的损失。纯无结构剪枝提供了最惊人的压缩率,并且不损失精度,但所产生的不规则稀疏性在存储大小之外并未提供效率提升,这使得它在推动 LLM 民主化的背景下吸引力较小。
我们探讨了显著性(saliency)概念,显著性指的是用于剪枝模型参数的重要性度量。最简单且易于理解的显著性估计是权重幅度,其中接近零的权重被认为不那么重要。尽管这种方法在理论上并不严谨(因为接近零的权重可能对模型性能至关重要),但它仍然非常有效,而且由于不需要复杂的计算,它保持了持久的流行性。另一方面,理论上严谨的显著性度量可以追溯到可训练神经网络的早期发展,并且已被证明比基于幅度的剪枝方法能够生成更优的模型,但这些早期方法所需的复杂计算对于今天的 LLM(大语言模型)来说难以扩展。幸运的是,现代的有志研究人员找到了更高效地计算这些显著性度量的方法,但遗憾的是,这些方法仍然需要计算梯度。在 2024 年最新的研究中,Bonsai 展示了通过仅使用前向传递的信息,就能够实现精确的剪枝,而不需要梯度。
尽管现代剪枝研究的主要驱动力是压缩当今顶级模型庞大体积,以便能够在合理大小的硬件上部署,但剪枝最初的动机是通过减少模型复杂性来提高模型的泛化能力。这种正则化效应无疑在剪枝后的 LLM 中产生了作用,这大概是一个好处,但这一点在当今的文献中研究较少。虽然通过正则化提高模型的泛化能力和减少过拟合已知是有益的,但在 LLM 的背景下,可能需要做出特别的考虑,因为根据使用场景,LLM 常常需要记住大量训练数据中的细节。因此,未来的研究应探讨在何种情况下,这种正则化会对 LLM 的预期应用产生不利影响。
在下一期中…
本文探讨的方法通过去除模型参数,提供了有效的模型压缩,这一过程被称为剪枝,但这只是模型压缩的一种方法。在下一篇文章中,我们将探讨量化在不同分辨率下的概念,并在合理的内存占用下,发展该主题的实用知识。
使用 Metaflow、AWS 和 Weights & Biases 优化物体检测
如何为物体检测创建生产级管道
·发表于Towards Data Science ·14 分钟阅读·2024 年 7 月 19 日
--
项目流程概述。图片来自作者。
目录
-
介绍(或标题中的内容)
-
没有 Ops 的 MLOps 现实
-
有效管理依赖关系
-
如何调试生产流程
-
找到合适的步长
-
要点总结
-
参考文献
相关链接
介绍(或标题中的内容)
在数据科学职位名称的世界中导航可能令人不知所措。以下是我最近在 LinkedIn 上看到的一些例子:
-
数据科学家
-
机器学习工程师
-
MLOps 工程师
-
数据科学家/机器学习工程师
-
机器学习性能工程师
-
…
话题还可以继续深入。让我们关注两个关键角色:数据科学家和机器学习工程师。根据 Chip Huyen 在她的书《Introduction to Machine Learning Interviews》中的描述1:
数据科学的目标是生成商业洞察,而机器学习工程的目标是将数据转化为产品。这意味着数据科学家往往更擅长统计学,而机器学习工程师则通常是更优秀的工程师。机器学习工程师肯定需要了解机器学习算法,而许多数据科学家则可以在不涉及机器学习的情况下完成他们的工作。
明白了。所以数据科学家必须懂得统计学,而机器学习工程师则必须了解机器学习算法。但如果数据科学的目标是产生商业洞察,并且到 2024 年,最强大的算法,特别是深度学习,往往能够产生最佳洞察,那么两者之间的界限就变得模糊了。或许这也能解释我们之前看到的数据科学家/机器学习工程师这一职位的结合?
Huyen 接着说:
随着公司对机器学习的采用逐步成熟,可能希望拥有一个专门的机器学习工程团队。然而,随着越来越多的预构建和预训练模型可以即插即用,开发机器学习模型可能不再需要那么多的机器学习知识,机器学习工程与数据科学将更加统一。
这是 2020 年写的。到 2024 年,机器学习工程和数据科学之间的界限确实变得模糊了。那么,如果实现机器学习模型的能力不是分界线,那么究竟是什么呢?
这一界限因实践者而异。如今,典型的数据科学家和机器学习工程师的区别如下:
-
数据科学家: 使用 Jupyter notebook,从未听说过 Airflow,Kaggle 专家,管道由手动按正确顺序执行代码单元组成,擅长超参数调优,Docker?夏天穿的好鞋!专注于开发。
-
机器学习工程师: 编写 Python 脚本,听说过 Airflow,但不喜欢它(支持 Prefect!),Kaggle 中级选手,自动化管道,模型调优交给数据科学家,Docker 爱好者。专注于生产环境。
在大公司中,数据科学家开发机器学习模型来解决业务问题,然后交给机器学习工程师。工程师将这些模型投入生产并进行部署,确保其可扩展性和鲁棒性。简而言之:今天数据科学家和机器学习工程师之间的根本区别,不在于谁在使用机器学习,而在于你是否专注于开发还是生产环境。
但如果你没有一家大公司,而是处于初创公司或小型公司的情况下,预算只能雇佣一位或几位数据科学团队成员呢?他们可能希望招聘能够兼做数据科学家/机器学习工程师的人员!为了成为这个神话般的“全栈数据科学家”,我决定将我之前的一个项目,使用 RetinaNet 和 KerasCV 进行物体检测,进行生产化(请参阅上述链接获取相关文章和代码)。原始项目是使用 Jupyter notebook 完成的,但存在一些不足之处:
-
以前没有模型版本控制、数据版本控制甚至代码版本控制。如果我的 Jupyter notebook 在某次运行时有效,而在随后的运行中无效,那时没有任何系统化的方法可以回到有效的脚本/模型(Ctrl + Z?Kaggle 中的保存 notebook 选项?)
-
模型评估相当简单,使用了 Matplotlib 和一些 KerasCV 图表。没有存储评估结果。
-
我们的计算资源受限于 Kaggle 免费的 20 小时 GPU。无法使用更大的计算实例,也不能并行训练多个模型。
-
该模型从未部署到任何端点,因此无法在 Jupyter notebook 以外的地方进行预测(没有业务价值)。
为了完成这个任务,我决定尝试使用 Metaflow。Metaflow 是一个开源的机器学习平台,旨在帮助数据科学家训练和部署机器学习模型。Metaflow 主要有两个功能:
-
一个工作流编排工具。Metaflow 将一个工作流分解为多个步骤。将一个 Python 函数转化为 Metaflow 步骤非常简单,只需在函数上方添加
@step
装饰器即可。Metaflow 并不一定具备像 Airflow 这样的工作流工具所提供的所有功能,但它简单、符合 Python 风格,并且可以设置使用 AWS Step Functions 作为外部编排器。此外,使用像 Airflow 或 Prefect 与 Metaflow 配合使用也是完全没问题的。 -
一个基础设施抽象工具。这正是 Metaflow 的真正优势所在。通常,数据科学家需要手动设置基础设施,将模型训练任务从他们的笔记本电脑发送到 AWS。这可能需要了解基础设施方面的知识,如 API 网关、虚拟私有云(VPC)、Docker/Kubernetes、子网掩码等。听起来这更像是机器学习工程师的工作!然而,通过使用 Cloud Formation 模板(基础设施即代码文件)和
@batch
Metaflow 装饰器,数据科学家能够以简单可靠的方式将计算任务发送到云端。
本文详细介绍了我使用 Metaflow、AWS 和 Weights & Biases 生产化物体检测模型的历程。我们将在这个过程中探讨四个关键的学习经验:
-
“没有 Ops 的 MLOps”现实
-
有效的依赖管理
-
生产环境工作流的调试策略
-
优化工作流结构
通过分享这些见解,我希望能指导你们这些数据从业者,从开发转向生产相关的工作,突出在这一过程中遇到的挑战和解决方案。
在深入具体内容之前,让我们先来看一下我们 Metaflow 管道的高层结构。这将为你提供一个鸟瞰视图,帮助你了解本文中讨论的工作流:
from metaflow import FlowSpec, Parameter, step, current, batch, S3, environment
class main_flow(FlowSpec):
@step
def start(self):
"""
Start-up: check everything works or fail fast!
"""
self.next(self.augment_data_train_model)
@batch(gpu=1, memory=8192, image='docker.io/tensorflow/tensorflow:latest-gpu', queue="job-queue-gpu-metaflow")
@step
def augment_data_train_model(self):
"""
Code to pull data from S3, augment it, and train our model.
"""
self.next(self.evaluate_model)
@step
def evaluate_model(self):
"""
Code to evaluate our detection model, using Weights & Biases.
"""
self.next(self.deploy)
@step
def deploy(self):
"""
Code to deploy our detection model to a Sagemaker endpoint
"""
self.next(self.end)
@step
def end(self):
"""
The final step!
"""
print("All done. \n\n Congratulations! Plants around the world will thank you. \n")
return
if __name__ == '__main__':
main_flow()
这个结构构成了我们生产级目标检测流水线的骨架。Metaflow 是 Python 风格的,使用装饰器将函数标记为流水线中的步骤,处理依赖关系管理,并将计算任务移到云端。步骤通过 self.next()
命令按顺序执行。更多关于 Metaflow 的内容,请参见 文档。
没有运维的 MLOps 现实
Metaflow 的一个承诺是数据科学家应该能够专注于他们关心的事情;通常是模型开发和特征工程(想想 Kaggle),同时将他们不关心的事情(计算任务在哪儿运行,数据存储在哪儿,等等)抽象化。对此有一句话:“没有运维的 MLOps”。我以为这意味着我能够抽象化 MLOps 工程师的工作,而无需自己学习或做太多运维工作。我以为我可以不用了解 Docker、CloudFormation 模板、EC2 实例类型、AWS 服务配额、Sagemaker 端点以及 AWS 批量配置。
不幸的是,这是天真了。我意识到许多 Metaflow 教程中链接的 CloudFormation 模板并没有提供从 AWS 配置 GPU 的方法(!)。这是在云端做数据科学的一个基本部分,因此缺乏文档令我感到惊讶。(我不是第一个对缺乏文档感到疑惑的人)
以下是一个代码片段,演示了在 Metaflow 中将作业发送到云端的样子:
@pip(libraries={'tensorflow': '2.15', 'keras-cv': '0.9.0', 'pycocotools': '2.0.7', 'wandb': '0.17.3'})
@batch(gpu=1, memory=8192, image='docker.io/tensorflow/tensorflow:latest-gpu', queue="job-queue-gpu-metaflow")
@environment(vars={
"S3_BUCKET_ADDRESS": os.getenv('S3_BUCKET_ADDRESS'),
'WANDB_API_KEY': os.getenv('WANDB_API_KEY'),
'WANDB_PROJECT': os.getenv('WANDB_PROJECT'),
'WANDB_ENTITY': os.getenv('WANDB_ENTITY')})
@step
def augment_data_train_model(self):
"""
Code to pull data from S3, augment it, and train our model.
"""
注意指定所需库和必要环境变量的重要性。因为计算任务是在云端运行的,它将无法访问你本地计算机上的虚拟环境或 .env
文件中的环境变量。使用 Metaflow 装饰器来解决这个问题既优雅又简单。
确实,你不必成为 AWS 专家才能在云端运行计算任务,但不要指望仅仅安装 Metaflow,使用默认的 CloudFormation 模板就能成功。没有运维的 MLOps 实在太美好,难以置信;也许这个短语应该是 没有运维的 MLOps;在学习了一些运维之后。
有效管理依赖关系
将一个开发项目转变为生产项目时,最重要的考虑因素之一是如何管理依赖关系。依赖关系指的是 Python 包,例如 TensorFlow、PyTorch、Keras、Matplotlib 等。
依赖管理类似于管理食谱中的食材,以确保一致性。一个食谱可能会说“加入一汤匙盐”。这在某种程度上是可重复的,但有经验的读者可能会问“Diamond Crystal 还是 Morton?”指定使用的盐的确切品牌可以最大程度地提高食谱的可重复性。
类似地,在机器学习中,依赖管理有不同的层次:
- 使用
requirements.txt
文件。这种简单的方式列出了所有带有固定版本的 Python 包。它工作得相当不错,但也有局限性:虽然你可以固定这些高层依赖,但无法固定任何传递依赖(依赖的依赖)。这使得创建可重复的环境变得非常困难,并且因为包被下载和安装,运行时也会变慢。例如:
pinecone==4.0.0
langchain==0.2.7
python-dotenv==1.0.1
pandas==2.2.2
streamlit==1.36.0
iso-639==0.4.5
prefect==2.19.7
langchain-community==0.2.7
langchain-openai==0.1.14
langchain-pinecone==0.1.1
这工作得相当不错,但也有局限性:虽然你可以固定这些高层依赖,但无法固定任何传递依赖(依赖的依赖)。这使得创建可重复的环境变得非常困难,并且因为包被下载和安装,运行时也会变慢。
- 使用 Docker 容器。这是黄金标准。它封装了整个环境,包括操作系统、库、依赖项和配置文件,使其非常一致且可重复。不幸的是,使用 Docker 容器可能会比较复杂,尤其是当数据科学家没有平台使用经验时。
Metaflow [@pypi/@conda](https://docs.metaflow.org/scaling/dependencies/libraries)
装饰器在这两种选项之间找到了一个折中方案,既轻量且简单,便于数据科学家使用,同时比requirements.txt
文件更具鲁棒性和可重复性。这些装饰器基本上执行以下操作:
-
为流程中的每一步创建独立的虚拟环境。
-
锁定 Python 解释器版本,而简单的
requirements.txt
文件做不到这一点。 -
为每一步解析完整的依赖图,并将其锁定以确保稳定性和可重复性。这个锁定的图被存储为元数据,便于审计和一致的环境重建。
-
将本地解析的环境传输到远程执行,即使远程环境的操作系统和 CPU 架构与客户端不同。
这比仅仅使用requirements.txt
文件要好得多,而且不需要数据科学家额外学习任何内容。
让我们回顾一下训练步骤,看看一个示例:
@pypi(libraries={'tensorflow': '2.15', 'keras-cv': '0.9.0', 'pycocotools': '2.0.7', 'wandb': '0.17.3'})
@batch(gpu=1, memory=8192, image='docker.io/tensorflow/tensorflow:latest-gpu', queue="job-queue-gpu-metaflow")
@environment(vars={
"S3_BUCKET_ADDRESS": os.getenv('S3_BUCKET_ADDRESS'),
'WANDB_API_KEY': os.getenv('WANDB_API_KEY'),
'WANDB_PROJECT': os.getenv('WANDB_PROJECT'),
'WANDB_ENTITY': os.getenv('WANDB_ENTITY')})
@step
def augment_data_train_model(self):
"""
Code to pull data from S3, augment it, and train our model.
"""
我们所要做的就是指定库和版本,Metaflow 会处理剩下的部分。
不幸的是,事情并非完全顺利。我的个人笔记本电脑是 Mac,但 AWS Batch 中的计算实例采用的是 Linux 架构。这意味着我们必须为 Linux 机器创建隔离的虚拟环境,而不是 Mac。这就需要所谓的交叉编译。我们只有在处理 .whl
(二进制)包时才能进行交叉编译。我们不能在尝试交叉编译时使用 .tar.gz
或其他源代码发行版。这是 pip
的一个特点,而不是 Metaflow 的问题。使用 @conda
装饰器是有效的(conda
似乎能够解决 pip
不能解决的问题),但如果我想利用 GPU 进行计算,则必须使用 conda 中的 tensorflow-gpu
包,这也带来了自己的问题。虽然有一些解决方法,但它们为我希望保持简洁的教程增加了太多复杂性。因此,我实际上不得不选择了 pip install -r requirements.txt
(使用了自定义 Python @pip
装饰器来实现)。虽然不太理想,但它确实有效。
如何调试生产环境中的流程
最初,使用 Metaflow 感觉有些慢。每次步骤失败时,我都需要添加打印语句并重新运行整个流程——这是一个耗时且代价高昂的过程,尤其是在计算密集型步骤中。
一旦我发现可以将流程变量作为工件存储,并且之后可以在 Jupyter notebook 中访问这些工件的值,我的迭代速度大大提升。例如,在处理 model.predict
调用的输出时,我将变量作为工件存储,以便于调试。以下是我如何做到的:
image = example["images"]
self.image = tf.expand_dims(image, axis=0) # Shape: (1, 416, 416, 3)
y_pred = model.predict(self.image)
confidence = y_pred['confidence'][0]
self.confidence = [conf for conf in confidence if conf != -1]
self.y_pred = bounding_box.to_ragged(y_pred)
在这里,model
是我经过充分训练的目标检测模型,image
是一张示例图像。当我在处理这个脚本时,我遇到了处理 model.predict
调用输出的问题。输出是什么类型的?输出的结构是什么样的?拉取示例图像的代码是否有问题?
为了检查这些变量,我使用 self._
语法将它们作为工件存储。任何可以被pickle序列化的对象都可以作为 Metaflow 工件存储。如果你跟随我的教程,这些工件将被存储在 Amazon S3 存储桶中,供以后引用。为了检查示例图像是否正确加载,我可以在我的本地计算机的同一代码库中打开 Jupyter notebook,并通过以下代码访问该图像:
import matplotlib.pyplot as plt
latest_run = Flow('main_flow').latest_run
step = latest_run['evaluate_model']
sample_image = step.task.data.image
sample_image = sample_image[0,:, :, :]
one_image_normalized = sample_image / 255
# Display the image using matplotlib
plt.imshow(one_image_normalized)
plt.axis('off') # Hide the axes
plt.show()
在这里,我们获取流程的最新运行,并通过在 Flow 调用中指定 main_flow
来确保获取到流程的信息。我存储的工件来自 evaluate_model
步骤,因此我指定了这一步骤。我通过调用 .data.image
获取图像数据。最后,我们可以绘制图像来检查并查看我们的测试图像是否有效,或者是否在管道的某个环节被破坏了:
在我的 Jupyter notebook 中输出的图像。图像来源:作者。
很棒,这和从 PlantDoc 数据集中下载的原始图像一致(尽管颜色看起来有些奇怪)。为了查看我们物体检测模型的预测结果,我们可以使用以下代码:
latest_run = Flow('main_flow').latest_run
step = latest_run['evaluate_model']
y_pred = step.task.data.y_pred
print(y_pred)
来自物体检测模型的预测。图片由作者提供。
输出结果似乎表明这个图像没有预测的边界框。这一点很有意思,可能有助于解释某个步骤为何表现异常或出现错误。
所有这些都可以通过一个简单的 Jupyter 笔记本完成,这是所有数据科学家都很熟悉的。那么,何时应将变量作为工件存储在 Metaflow 中呢?Ville Tuulos 给出了一个启发式的方法2:
一条经验法则:使用实例变量(例如 self)来存储任何可能在步骤外部有用的数据和对象。仅将本地变量用于中间的临时数据。如果不确定,使用实例变量,因为它们使调试更加容易。
如果你在使用 Metaflow,请从我的经验中吸取教训:充分利用工件和 Jupyter 笔记本,使调试在生产级项目中变得轻松。
关于调试的另一个注意事项:如果一个流程在某个特定步骤失败,且你希望从该失败步骤重新运行流程,可以在 Metaflow 中使用resume
命令。这样可以加载之前步骤的所有相关输出,而无需重新执行它们,从而节省时间。直到我尝试了Prefect,才意识到那里并没有一个简单的方法来做到这一点。
寻找合适的步骤大小
Goldilocks步骤的大小应该是多少?理论上,你可以把整个脚本塞进一个巨大的pull_and_augment_data_and_train_model_and_evaluate_model_and_deploy
步骤中,但这样并不可取。如果流程中的某个部分失败,你就不能轻松使用resume
功能跳过重新运行整个流程。
相反,将脚本拆分为一百个微小步骤也是可能的,但这同样不推荐。存储工件和管理步骤会带来一定的开销,拥有一百个步骤会占据大部分执行时间。为了找到一个合适的步骤大小,Tuulos 告诉我们:
一条经验法则:将工作流结构化为逻辑清晰、容易解释和理解的步骤。如果不确定,倾向于选择较小的步骤。小步骤往往比大步骤更容易理解和调试。
最初,我将我的流程结构设计为这些步骤:
-
增强数据
-
训练模型
-
评估模型
-
部署模型
在增强数据后,我需要将数据上传到一个 S3 存储桶,然后在train
步骤中下载增强后的数据,用于模型训练,原因有两个:
-
augment
步骤将在我的本地笔记本上进行,而train
步骤则会发送到云端的 GPU 实例。 -
Metaflow 的工件通常用于在步骤之间传递数据,但它无法处理 TensorFlow 数据集对象,因为它们不能被 pickle。于是我将它们转换为
tfrecords
格式并上传到 S3。
这个上传/下载过程花费了很长时间。因此,我将数据增强和训练步骤合并为一个步骤。这样减少了流程的运行时间和复杂性。如果你感兴趣,可以查看我 GitHub 仓库中的 separate_augement_train
分支,该版本包含了分开的步骤。
主要收获
在本文中,我讨论了在将我的目标检测项目投入生产时所经历的一些高峰和低谷。简要总结如下:
-
你必须学习一些操作,才能在没有操作的情况下实现 MLOps。但在学习了一些基础的设置后,你将能够仅使用 Python 装饰器将计算任务发送到 AWS。本文附带的代码库介绍了如何在 AWS 中配置 GPU,因此如果这是你的目标之一,请仔细研究。
-
依赖管理是生产中的一个关键步骤。
requirements.txt
文件是最基本的要求,Docker 是黄金标准,而 Metaflow 提供了一条中间路径,适用于许多项目。只不过这个项目不适用,不幸的是。 -
在 Metaflow 中,使用工件和 Jupyter 笔记本可以方便地进行调试。使用
resume
功能可以避免重新运行时间或计算密集型的步骤。 -
在将脚本拆分为适合 Metaflow 流程的步骤时,尽量将步骤拆分成合理大小的小步骤,倾向于使用较小的步骤。但如果开销过大,也不要害怕合并步骤。
这个项目仍然有一些我希望改进的方面。一个方面是添加更多的数据,这样我们就能在更多种类的植物上检测疾病。另一个方面是为项目添加前端,允许用户上传图片并按需获取物体检测。像 Streamlit 这样的库非常适合这个功能。最后,我希望最终模型的性能能达到最先进的水平。Metaflow 具备并行训练多个模型的能力,这将有助于实现这一目标。不幸的是,这需要大量的计算资源和资金,但这是任何最先进模型所必需的。
参考文献
1 C. Huyen, 《机器学习面试简介》(2021), 自出版
2 V. Tuulos, 《高效的数据科学基础设施》 (2022), Manning 出版社
Streamlit 支持 5 个重要的数据可视化库 — 该选择哪个?
我们将使用 Altair、Bokeh、Plotly、Pandas Plot 和 Matplotlib 编写示例代码,以说明每个库的优缺点。
·发布于 Towards Data Science ·17 分钟阅读·2024 年 5 月 16 日
--
图片由作者借助 Microsoft Image Creator 创建
想象一下,你的组织已经决定将 Streamlit 作为展示数据可视化应用的主要平台,并且为了确保一致的外观,它希望采用一个图表库,供所有应用使用。假设你的工作是调查哪个库最合适。
你有很多选择!你可以使用 5 个库来编码你的数据可视化:Altair、Bokeh、Plotly、Pyplot(Matplotlib)和 Vega Lite。而 Streamlit 也提供了一些原生图表。
让我们逐一看看这些库,并编写一些常用的图表。
我们将使用的一组数据是两种加密货币的价格数据,从这些数据中,我们将创建一张折线图,显示两种货币在一年内收盘价的变化;一张分组条形图,显示相同的数据,同样适用于两种货币;以及一张带有趋势线的散点图,展示这两种货币价值变化之间的相关性。
结构与关系:图神经网络及其在 Pytorch 中的实现
了解图神经网络的数学背景及其在 pytorch 中回归问题的实现
·发表于 Towards Data Science ·12 分钟阅读·2024 年 3 月 5 日
--
简介
相互连接的图形数据无处不在,从分子结构到社交网络以及城市的设计结构。图神经网络(GNN)正逐渐成为一种强大的方法,用于建模和学习此类数据的空间和图形结构。它已被应用于蛋白质结构及其他分子应用,如药物发现,并且还被用于建模如社交网络等系统。最近,标准的 GNN 与其他机器学习模型的思想相结合,开发出了一些令人兴奋的创新应用。其中一项发展是将 GNN 与序列模型结合 —— 空间-时间图神经网络(Spatio-Temporal GNN),它能够捕捉数据的时间和空间(因此得名)依赖性,仅此一点就可以应用于行业/研究中的许多挑战/问题。
尽管图神经网络(GNN)取得了令人兴奋的进展,但相关资源仍然很少,这使得它对许多人来说难以接触。在这篇简短的文章中,我想提供一个图神经网络的简要介绍,涵盖数学描述以及使用 pytorch 库的回归问题。通过揭示 GNN 背后的原理,我们能够更深入地理解其能力和应用。
图神经网络的数学描述
图 G 可以定义为 G = (V, E),其中 V 是节点集合,E 是它们之间的边。图通常通过邻接矩阵 A 来表示,表示节点之间边的存在与否,即 aij 的值为 1 表示节点 i 和节点 j 之间有边(连接),否则为 0。如果图有 n 个节点,则 A 的维度为 (n × n)。邻接矩阵在 图 1 中演示。
图 1. 三个不同图的邻接矩阵
每个节点(以及边!但为了简化,我们稍后会回到边)将有一组特征(例如,如果节点是一个人,特征可能包括年龄、性别、身高、职业等)。如果每个节点有 f 个特征,则特征矩阵 X 为 (n × f)。在某些问题中,每个节点可能还具有目标标签,该标签可能是一组分类标签或数值(如 图 2 所示)。
单节点计算
为了学习任何节点与其邻居之间的相互依赖关系,我们需要考虑其邻居的特征。这使得 GNN 能够通过图来学习数据的结构表示。考虑一个节点 j 及其 Nj 个邻居,GNN 会转换每个邻居的特征,聚合这些特征,然后更新节点 i 的特征空间。以下是这些步骤的描述。
图 2. 一个节点(j)的示意图,具有特征 xj 和标签 yj,以及邻居节点(i、2、3),每个节点都有自己的特征嵌入和相应的标签。
邻居特征转换可以通过多种方式进行,例如通过 MLP 网络或线性变换,例如
其中 w 和 b 表示变换的权重和偏置。信息聚合,即来自每个邻居节点的信息会被聚合:
聚合步骤的性质可以有多种不同的方法,例如求和、平均、最小/最大池化和拼接:
在聚合步骤之后,最后一步是更新节点 j:
更新可以通过 MLP 使用拼接的节点特征和邻居信息聚合(mj)来完成,或者我们可以使用线性变换,即
其中 U 是一个可学习的权重矩阵,它通过非线性激活函数(此处使用 ReLU)将原始节点特征(xj)与聚合的邻居特征(mj)结合起来。这就是在单层中更新单个节点的过程,相同的过程应用于图中的所有其他节点,数学上,这可以通过邻接矩阵来表示。
图级计算
对于一个包含 n 个节点的图,每个节点有f个特征,我们可以将所有特征连接成一个矩阵:
因此,邻居特征变换和聚合步骤可以写作:
其中 I 是单位矩阵,这有助于包括每个节点的自身特征,否则我们只考虑来自节点 j 邻居的变换特征,而不考虑其自身特征。最后一步是根据连接数对每个节点进行归一化,即对于具有 Nj 个连接的节点 j,特征变换可以这样进行:
上面的方程可以调整为:
其中 D 是度矩阵,是每个节点连接数的对角矩阵。然而,更常见的是,这一步归一化是通过以下方式进行的:
这就是图卷积网络(GCN)方法,它使得 GNN 能够学习节点之间的结构和关系。然而,GCN 的一个问题是邻居特征变换的权重向量在所有邻居中是共享的,即所有邻居被认为是相等的,但通常并非如此,因此不能很好地代表真实系统。为了解决这个问题,可以使用图注意力网络(GAT)来计算邻居特征对目标节点的重要性,从而允许不同的邻居根据它们的相关性以不同的方式贡献于目标节点特征的更新。注意力系数是通过一个可学习的矩阵来确定的,如下所示:
其中 W 是共享的可学习特征线性变换,Wa 是一个可学习的权重向量,eij 是原始的注意力分数,表示节点 i 的特征对节点 j 的重要性。注意力分数使用 SoftMax 函数进行归一化:
现在,可以使用注意力系数来计算特征聚合步骤:
这就是单层的情况,我们可以构建多个层来增加模型的复杂性,这在图 3中进行了演示。增加层数将允许模型学习更多的全局特征,并捕捉更复杂的关系,但也很容易导致过拟合,因此应始终使用正则化技术来防止过拟合。
图 3. 一个多层 GNN 模型的示意图
最后,一旦从网络中获得所有节点的最终特征向量,就可以形成特征矩阵 H:
该特征矩阵可以用于执行多种任务,例如节点或图的分类。这也将是 GCN/GAT 数学描述部分的结束。
GCN 回归示例
让我们实现一个回归示例,目标是训练一个网络来预测一个节点的值,前提是已知其他所有节点的值,即每个节点有一个单一的特征(这是一个标量值)。本示例的目的是利用图中编码的固有关系信息,准确预测每个节点的数值。需要注意的是,我们输入所有节点的数值(除了目标节点,目标节点的值用 0 进行掩码),然后预测目标节点的值。对于每个数据点,我们对所有节点重复这一过程。也许这看起来像是一个奇怪的任务,但让我们看看是否能够根据其他节点的值预测任何节点的预期值。使用的数据是来自工业的传感器系列的相应仿真数据,下面示例中的图结构是基于实际过程结构的。我在代码中提供了注释,以便更容易理解。你可以在这里找到数据集的副本(注意:这是我自己的数据,通过仿真生成)。
这段代码和训练过程远未优化,但它的目的是展示 GNN 的实现,并让人对其工作原理有直观的理解。我目前的实现方式中有一个问题,除了用于学习目的外,这种方法绝对不应该继续使用,那就是将节点特征值进行掩码,并从邻居节点的特征中预测它。当前你需要对每个节点进行循环(效率不高),一种更好的方法是在聚合步骤中停止模型将自身特征包含进来,这样你就不需要一个节点一个节点地处理。但我认为通过当前的方法更容易为模型构建直观理解:)
数据预处理
导入必要的库和从 CSV 文件中读取传感器数据。将所有数据归一化到 0 到 1 的范围内。
import pandas as pd
import torch
from torch_geometric.data import Data, Batch
from sklearn.preprocessing import StandardScaler, MinMaxScaler
from sklearn.model_selection import train_test_split
import numpy as np
from torch_geometric.data import DataLoader
# load and scale the dataset
df = pd.read_csv('SensorDataSynthetic.csv').dropna()
scaler = MinMaxScaler()
df_scaled = pd.DataFrame(scaler.fit_transform(df), columns=df.columns)
使用 PyTorch 张量定义图中节点之间的连接性(边缘索引)——即这提供了系统的图形拓扑。
nodes_order = [
'Sensor1', 'Sensor2', 'Sensor3', 'Sensor4',
'Sensor5', 'Sensor6', 'Sensor7', 'Sensor8'
]
# define the graph connectivity for the data
edges = torch.tensor([
[0, 1, 2, 2, 3, 3, 6, 2], # source nodes
[1, 2, 3, 4, 5, 6, 2, 7] # target nodes
], dtype=torch.long)
从 CSV 导入的数据具有表格结构,但为了在 GNN 中使用,必须将其转换为图形结构。数据的每一行(一个观察值)表示一个图。迭代每一行以创建数据的图形表示。
为每个节点/传感器创建一个掩码,以指示数据的存在(1)或缺失(0),从而提供处理缺失数据的灵活性。在大多数系统中,可能存在没有数据的项目,因此需要处理缺失数据的灵活性。将数据划分为训练集和测试集。
graphs = []
# iterate through each row of data to create a graph for each observation
# some nodes will not have any data, not the case here but created a mask to allow us to deal with any nodes that do not have data available
for _, row in df_scaled.iterrows():
node_features = []
node_data_mask = []
for node in nodes_order:
if node in df_scaled.columns:
node_features.append([row[node]])
node_data_mask.append(1) # mask value of to indicate present of data
else:
# missing nodes feature if necessary
node_features.append(2)
node_data_mask.append(0) # data not present
node_features_tensor = torch.tensor(node_features, dtype=torch.float)
node_data_mask_tensor = torch.tensor(node_data_mask, dtype=torch.float)
# Create a Data object for this row/graph
graph_data = Data(x=node_features_tensor, edge_index=edges.t().contiguous(), mask = node_data_mask_tensor)
graphs.append(graph_data)
#### splitting the data into train, test observation
# Split indices
observation_indices = df_scaled.index.tolist()
train_indices, test_indices = train_test_split(observation_indices, test_size=0.05, random_state=42)
# Create training and testing graphs
train_graphs = [graphs[i] for i in train_indices]
test_graphs = [graphs[i] for i in test_indices]
图形可视化
使用上述边缘索引创建的图结构可以通过 networkx 进行可视化。
import networkx as nx
import matplotlib.pyplot as plt
G = nx.Graph()
for src, dst in edges.t().numpy():
G.add_edge(nodes_order[src], nodes_order[dst])
plt.figure(figsize=(10, 8))
pos = nx.spring_layout(G)
nx.draw(G, pos, with_labels=True, node_color='lightblue', edge_color='gray', node_size=2000, font_weight='bold')
plt.title('Graph Visualization')
plt.show()
模型定义
让我们定义模型。该模型包含两个 GAT 卷积层,第一个层将节点特征转换到 8 维空间,第二个 GAT 层则将其进一步缩减为 8 维表示。
GNN(图神经网络)对过拟合非常敏感,正则化(dropout)会在每个 GAT 层后应用,并使用用户定义的概率来防止过拟合。dropout 层在训练过程中随机将输入张量的某些元素置零。
GAT 卷积层的输出结果通过一个全连接(线性)层,以将 8 维的输出映射到最终的节点特征,在本例中,每个节点对应一个标量值。
对目标节点的值进行掩码处理;如前所述,本任务的目的是基于邻居节点的值回归目标节点的值。这也是将目标节点的值掩码或替换为零的原因。
from torch_geometric.nn import GATConv
import torch.nn.functional as F
import torch.nn as nn
class GNNModel(nn.Module):
def __init__(self, num_node_features):
super(GNNModel, self).__init__()
self.conv1 = GATConv(num_node_features, 16)
self.conv2 = GATConv(16, 8)
self.fc = nn.Linear(8, 1) # Outputting a single value per node
def forward(self, data, target_node_idx=None):
x, edge_index = data.x, data.edge_index
edge_index = edge_index.T
x = x.clone()
# Mask the target node's feature with a value of zero!
# Aim is to predict this value from the features of the neighbours
if target_node_idx is not None:
x[target_node_idx] = torch.zeros_like(x[target_node_idx])
x = F.relu(self.conv1(x, edge_index))
x = F.dropout(x, p=0.05, training=self.training)
x = F.relu(self.conv2(x, edge_index))
x = F.relu(self.conv3(x, edge_index))
x = F.dropout(x, p=0.05, training=self.training)
x = self.fc(x)
return x
训练模型
初始化模型并定义优化器、损失函数以及包括学习率、权重衰减(用于正则化)、批处理大小和训练轮次在内的超参数。
model = GNNModel(num_node_features=1)
batch_size = 8
optimizer = torch.optim.Adam(model.parameters(), lr=0.0002, weight_decay=1e-6)
criterion = torch.nn.MSELoss()
num_epochs = 200
train_loader = DataLoader(train_graphs, batch_size=1, shuffle=True)
model.train()
训练过程相对标准,每个图(一个数据点)都会通过模型的前向传播(遍历每个节点并预测目标节点)。预测产生的损失会在定义的批处理大小上累积,然后通过反向传播更新 GNN。
for epoch in range(num_epochs):
accumulated_loss = 0
optimizer.zero_grad()
loss = 0
for batch_idx, data in enumerate(train_loader):
mask = data.mask
for i in range(1,data.num_nodes):
if mask[i] == 1: # Only train on nodes with data
output = model(data, i) # get predictions with the target node masked
# check the feed forward part of the model
target = data.x[i]
prediction = output[i].view(1)
loss += criterion(prediction, target)
#Update parameters at the end of each set of batches
if (batch_idx+1) % batch_size == 0 or (batch_idx +1 ) == len(train_loader):
loss.backward()
optimizer.step()
optimizer.zero_grad()
accumulated_loss += loss.item()
loss = 0
average_loss = accumulated_loss / len(train_loader)
print(f'Epoch {epoch+1}, Average Loss: {average_loss}')
测试训练好的模型
使用测试数据集,将每个图像通过训练后的模型的前向传播,并根据每个节点的邻居节点的值预测其值。
test_loader = DataLoader(test_graphs, batch_size=1, shuffle=True)
model.eval()
actual = []
pred = []
for data in test_loader:
mask = data.mask
for i in range(1,data.num_nodes):
output = model(data, i)
prediction = output[i].view(1)
target = data.x[i]
actual.append(target)
pred.append(prediction)
可视化测试结果
使用 iplot,我们可以将节点的预测值与真实值进行可视化对比。
import plotly.graph_objects as go
from plotly.offline import iplot
actual_values_float = [value.item() for value in actual]
pred_values_float = [value.item() for value in pred]
scatter_trace = go.Scatter(
x=actual_values_float,
y=pred_values_float,
mode='markers',
marker=dict(
size=10,
opacity=0.5,
color='rgba(255,255,255,0)',
line=dict(
width=2,
color='rgba(152, 0, 0, .8)',
)
),
name='Actual vs Predicted'
)
line_trace = go.Scatter(
x=[min(actual_values_float), max(actual_values_float)],
y=[min(actual_values_float), max(actual_values_float)],
mode='lines',
marker=dict(color='blue'),
name='Perfect Prediction'
)
data = [scatter_trace, line_trace]
layout = dict(
title='Actual vs Predicted Values',
xaxis=dict(title='Actual Values'),
yaxis=dict(title='Predicted Values'),
autosize=False,
width=800,
height=600
)
fig = dict(data=data, layout=layout)
iplot(fig)
尽管没有对模型架构或超参数进行精细调优,实际上模型表现不错,我们可以进一步调优模型以提高准确性。
这篇文章到此为止。GNN 相较于其他机器学习分支来说相对较新,看到这个领域的发展以及它应用于不同问题将是非常令人兴奋的。最后,感谢你花时间阅读这篇文章,希望它对你理解 GNN 或其数学背景有所帮助。
在你离开之前
个人而言,我非常喜欢花时间学习新概念,并将这些概念应用于新的问题和挑战,我相信大多数阅读这些文章的人也有同样的感受。我认为能够做这件事是一种特权,每个人都应该拥有这种特权,但并不是每个人都能拥有。我们每个人都有责任改变这一点,为每个人创造更加光明的未来。请考虑向 UniArk 捐款(UniArk.org),以帮助来自世界上常常被大学和国家忽视的群体——受迫害的少数群体(无论是种族、宗教还是其他方面)的才华横溢的学生。UniArk 深入寻找最遥远、最贫困地区的人才与潜力——那些发展中国家的偏远地区。您的捐款将成为压迫社会中某个人的希望灯塔。我希望您能帮助 UniArk 保持这盏灯塔的光明。
除非另有注明,所有图片均由作者提供
结构化生成式 AI
如何限制你的模型输出定义的格式
·发表于 Towards Data Science ·阅读时间 7 分钟·2024 年 4 月 18 日
--
在这篇文章中,我将解释并演示“结构化生成式 AI”的概念:即将生成式 AI 限制在定义的格式内。文章结束时,你将理解它的应用场景以及如何实现它,无论是从零开始构建一个变换器模型,还是使用 Hugging Face 的模型。此外,我们还将介绍一个与分词相关的重要技巧,特别适用于结构化语言。
生成式 AI 的许多用途之一是作为翻译工具。这通常涉及在人类语言之间的翻译,但也可以包括计算机语言或格式。例如,你的应用程序可能需要将自然语言(人类语言)翻译成 SQL:
**Natural language**: “Get customer names and emails of customers from the US”
**SQL**: "SELECT name, email FROM customers WHERE country = 'USA'"
或者将文本数据转换为 JSON 格式:
**Natural language**: “I am John Doe, phone number is 555–123–4567,
my friends are Anna and Sara”
**JSON**: {name: "John Doe",
phone_number: "555–123–5678",
friends: {
name: [["Anna", "Sara"]]}
}
自然地,其他结构化语言也可以有更多的应用。此类任务的训练过程包括将自然语言与结构化格式的示例输入到编码器-解码器模型中。或者,利用预训练的语言模型(LLM)也可以满足需求。
虽然实现 100% 准确率是不可能的,但有一类错误我们是可以消除的:语法错误。这些错误是对语言格式的违反,比如用点替代逗号,使用 SQL 模式中没有的表名,或者遗漏括号闭合,这些都会导致 SQL 或 JSON 无法执行。
我们正在翻译成结构化语言,这意味着在每一步生成过程中,合法令牌的列表是有限的并且是预定的。如果我们能够将这一知识注入到生成式 AI 过程中,就能避免许多不正确的结果。这就是结构化生成式 AI 的理念:将其限制为一组合法的令牌。
关于令牌生成的快速提醒
无论是使用编码器-解码器架构还是 GPT 架构,令牌生成都是按顺序进行的。每个令牌的选择依赖于输入和之前生成的令牌,直到生成
解码器分类器为词汇表中的每个令牌分配一个 logit(图片由作者提供)
限制令牌生成
为了约束令牌生成,我们结合了对输出语言结构的理解。不合法的令牌将其 logits 设置为-inf,确保它们不会被选中。例如,如果在“Select name”后只有逗号或“FROM”是合法的,那么所有其他令牌的 logits 都会被设置为-inf。
如果你使用 Hugging Face,可以通过“logits 处理器”实现这一点。要使用它,你需要实现一个包含 call 方法的类,该方法在计算 logits 后被调用,但在采样之前。此方法接收所有令牌 logits 和生成的输入 ID,并返回所有令牌的修改后的 logits。
从 logits 处理器返回的 logits:所有不合法的令牌都会得到-inf 的值(图片由作者提供)
我将通过一个简化的示例演示代码。首先,我们初始化模型,这里我们使用 Bart 模型,但任何模型都可以使用。
from transformers import BartForConditionalGeneration, BartTokenizerFast, PreTrainedTokenizer
from transformers.generation.logits_process import LogitsProcessorList, LogitsProcessor
import torch
name = 'facebook/bart-large'
tokenizer = BartTokenizerFast.from_pretrained(name, add_prefix_space=True)
pretrained_model = BartForConditionalGeneration.from_pretrained(name)
如果我们想要生成从自然语言到 SQL 的翻译,可以运行:
to_translate = 'customers emails from the us'
words = to_translate.split()
tokenized_text = tokenizer([words], is_split_into_words=True)
out = pretrained_model.generate(
torch.tensor(tokenized_text["input_ids"]),
max_new_tokens=20,
)
print(tokenizer.convert_tokens_to_string(
tokenizer.convert_ids_to_tokens(
out[0], skip_special_tokens=True)))
返回
'More emails from the us'
由于我们没有针对文本到 SQL 任务对模型进行微调,因此输出不类似于 SQL。在本教程中,我们不会训练模型,但我们会引导它生成 SQL 查询。我们将通过使用一个函数来实现这一点,该函数将每个生成的令牌映射到允许的下一个令牌列表。为了简化,我们仅关注紧接着的前一个令牌,但更复杂的机制也容易实现。我们将使用一个字典来定义每个令牌允许的后续令牌。例如,查询必须以“SELECT”或“DELETE”开始,在“SELECT”之后,仅允许“name”、“email”或“id”,因为这些是我们架构中的列。
rules = {'<s>': ['SELECT', 'DELETE'], # beginning of the generation
'SELECT': ['name', 'email', 'id'], # names of columns in our schema
'DELETE': ['name', 'email', 'id'],
'name': [',', 'FROM'],
'email': [',', 'FROM'],
'id': [',', 'FROM'],
',': ['name', 'email', 'id'],
'FROM': ['customers', 'vendors'], # names of tables in our schema
'customers': ['</s>'],
'vendors': ['</s>'], # end of the generation
}
现在我们需要将这些令牌转换为模型使用的 ID。这将在一个继承自 LogitsProcessor 的类中完成。
def convert_token_to_id(token):
return tokenizer(token, add_special_tokens=False)['input_ids'][0]
class SQLLogitsProcessor(LogitsProcessor):
def __init__(self, tokenizer: PreTrainedTokenizer):
self.tokenizer = tokenizer
self.rules = {convert_token_to_id(k): [convert_token_to_id(v0) for v0 in v] for k,v in rules.items()}
最后,我们将实现 call 函数,该函数在计算 logits 后被调用。此函数创建一个包含-infs 的新张量,检查哪些 ID 符合规则(字典中的规则),并将其分数放入新张量中。结果是一个仅包含有效令牌的有效值的张量。
class SQLLogitsProcessor(LogitsProcessor):
def __init__(self, tokenizer: PreTrainedTokenizer):
self.tokenizer = tokenizer
self.rules = {convert_token_to_id(k): [convert_token_to_id(v0) for v0 in v] for k,v in rules.items()}
def __call__(self, input_ids: torch.LongTensor, scores: torch.LongTensor):
if not (input_ids == self.tokenizer.bos_token_id).any():
# we must allow the start token to appear before we start processing
return scores
# create a new tensor of -inf
new_scores = torch.full((1, self.tokenizer.vocab_size), float('-inf'))
# ids of legitimate tokens
legit_ids = self.rules[int(input_ids[0, -1])]
# place their values in the new tensor
new_scores[:, legit_ids] = scores[0, legit_ids]
return new_scores
就这样!我们现在可以使用 logits 处理器进行生成:
to_translate = 'customers emails from the us'
words = to_translate.split()
tokenized_text = tokenizer([words], is_split_into_words=True, return_offsets_mapping=True)
logits_processor = LogitsProcessorList([SQLLogitsProcessor(tokenizer)])
out = pretrained_model.generate(
torch.tensor(tokenized_text["input_ids"]),
max_new_tokens=20,
logits_processor=logits_processor
)
print(tokenizer.convert_tokens_to_string(
tokenizer.convert_ids_to_tokens(
out[0], skip_special_tokens=True)))
返回
SELECT email , email , id , email FROM customers
结果有点奇怪,但请记住:我们甚至没有训练模型! 我们只是根据特定规则强制生成标记。值得注意的是,约束生成不会干扰训练;约束只在训练后生成过程中起作用。因此,当这些约束得当实施时,它们只会提高生成准确性。
我们的简化实现未能涵盖所有 SQL 语法。一个真正的实现必须支持更多的语法,可能不仅考虑最后一个标记,还要考虑多个标记,并且支持批量生成。一旦这些改进到位,我们训练好的模型可以可靠地生成可执行的 SQL 查询,且仅限于模式中有效的表名和列名。类似的方法也可以在生成 JSON 时强制执行约束,确保键存在和括号闭合。
注意分词问题
分词通常被忽视,但在使用生成性 AI 进行结构化输出时,正确的分词至关重要。然而,在后台,分词可能对模型的训练产生影响。例如,您可能会微调一个模型,将文本翻译为 JSON。在微调过程中,您向模型提供文本-JSON 对,模型会对其进行分词。那么这种分词会是什么样子呢?
(图像来源:作者)
当您阅读“[”时,它是两个方括号,但分词器将其转换为单一的 ID,这将被分词分类器视为与单一括号完全不同的类别。这使得模型必须学习的整个逻辑更加复杂(例如,记住需要关闭多少个括号)。类似地,在单词前添加空格可能会改变它们的分词和类别 ID。例如:
![(图像来源:作者)这再次增加了模型需要学习的逻辑复杂性,因为与这些 ID 相关的权重将需要单独学习,以适应稍微不同的情况。为了简化学习,确保每个概念和标点符号始终转换为相同的标记,方法是为单词和字符前添加空格。
分开写的单词有助于更一致的分词(图像来源:作者)
在微调过程中输入带空格的示例可以简化模型需要学习的模式,从而提高模型的准确性。在预测时,模型将输出带空格的 JSON,您可以在解析之前去除这些空格。
总结
生成性 AI 提供了一种有价值的方法,用于翻译成格式化语言。通过利用输出结构的知识,我们可以约束生成过程,消除一类错误,并确保查询的可执行性和数据结构的可解析性。
此外,这些格式可能会使用标点符号和关键词来表示某些意义。确保这些关键词的标记化一致性,可以显著降低模型需要学习的模式复杂度,从而减少模型的所需规模和训练时间,同时提高其准确性。
结构化生成式人工智能可以有效地将自然语言转换为任何结构化格式。这些翻译能够从文本中提取信息或生成查询,这是许多应用的强大工具。
使用 Ollama 进行结构化 LLM 输出
有效控制你的模型响应
·发表于Towards Data Science ·阅读时间:9 分钟·2024 年 12 月 17 日
--
在 0.5 版本中,Ollama 对其 LLM API 进行了重大增强。通过引入结构化输出,Ollama 现在使得可以将模型输出约束为由 JSON 模式定义的特定格式。在背后,大多数系统使用 Pydantic 的功能来实现这一点。
图片来自作者(Dalle-3)
结构化输出解决了许多开发者面临的一个棘手问题,即当一个系统或过程从 LLM 获取输出以进一步处理时。对于该系统来说,了解期望的输入格式并能够准确处理它是非常重要的,这样每次都能得到可重复的结果。
同样,你希望每次向用户展示模型输出时,使用相同的格式,以避免混淆和错误。
到目前为止,确保大多数模型输出格式一致一直是个痛点,但 Ollama 的新功能让这一过程变得非常简单,正如我在示例代码片段中展示的那样。
但在此之前,你需要安装 Ollama 的最新版本。这不是关于 Ollama 或如何运行它的教程。如果你想要这些信息,请点击我下面的文章,我会详细介绍所有相关内容。
结构化输出及其使用方法
在 LLM 应用中构建鲁棒性和确定性
·发表于Towards Data Science ·阅读时间:5 分钟·2024 年 8 月 9 日
--
作者提供的图片
OpenAI 最近宣布支持其最新的gpt-4o-2024–08–06模型中的结构化输出。对于大型语言模型(LLM)而言,结构化输出并不是什么新鲜事——开发者们通常使用各种提示工程技术,或者第三方工具。
在本文中,我们将解释什么是结构化输出,它是如何工作的,以及如何在你自己的基于 LLM 的应用中应用它们。虽然 OpenAI 的公告使得通过其 API 实现结构化输出变得相当简单(如我们在此演示的),你可能更倾向于选择开源的Outlines包(由dotxt的可爱团队维护),因为它既可以应用于自托管的开源模型(如 Mistral 和 LLaMA),也可以应用于专有 API。(免责声明:由于这个问题,截至本文写作时,Outlines 尚不支持通过 OpenAI API 生成结构化 JSON;但这一点很快就会有所改变!)
什么是结构化输出?
如果RedPajama 数据集可以作为参考,那么压倒性的预训练数据来自人类文本。因此,“自然语言”是 LLM 的本土领域——无论是在输入端,还是输出端。然而,当我们构建应用程序时,我们希望使用机器可读的正式结构或模式来封装我们的数据输入/输出。通过这种方式,我们在应用程序中构建了鲁棒性和确定性。
结构化输出是一种强制执行预定义模式的机制,作用于 LLM 的输出。这通常意味着我们强制执行一个 JSON 模式,然而它不限于 JSON—我们原则上可以强制执行 XML、Markdown 或完全自定义的模式。结构化输出的好处有两个方面:
-
更简单的提示设计—我们在指定输出的格式时,不需要过于冗长。
-
确定性的名称和类型—我们可以保证例如在 LLM 的响应中获得一个具有
Number
JSON 类型的age
属性。
实现一个 JSON 模式
对于这个例子,我们将使用Sam Altman 的维基百科条目中的第一句话…
Samuel Harris Altman(1985 年 4 月 22 日出生)是美国企业家和投资者,最著名的是自 2019 年起担任 OpenAI 的首席执行官(他在 2023 年 11 月被短暂解雇并恢复职务)。
…并且我们将使用最新的 GPT-4o 检查点作为命名实体识别(NER)系统。我们将强制执行以下 JSON 模式:
json_schema = {
"name": "NamedEntities",
"schema": {
"type": "object",
"properties": {
"entities": {
"type": "array",
"description": "List of entity names and their corresponding types",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The actual name as specified in the text, e.g. a person's name, or the name of the country"
},
"type": {
"type": "string",
"description": "The entity type, such as 'Person' or 'Organization'",
"enum": ["Person", "Organization", "Location", "DateTime"]
}
},
"required": ["name", "type"],
"additionalProperties": False
}
}
},
"required": ["entities"],
"additionalProperties": False
},
"strict": True
}
从本质上讲,我们的 LLM 响应应该包含一个NamedEntities
对象,其中包含一个entities
数组,每个数组元素都包含一个name
和type
。这里有几点需要注意。例如,我们可以强制使用Enum类型,这在 NER 中非常有用,因为我们可以将输出限制为一组固定的实体类型。我们必须在required
数组中指定所有字段;然而,我们也可以通过将类型设置为例如["string", null]
来模拟“可选”字段。
我们现在可以将我们的模式、数据和指令传递给 API。我们需要用dict填充response_format
参数,在其中将type
设置为"json_schema"
,然后提供相应的模式。
completion = client.beta.chat.completions.parse(
model="gpt-4o-2024-08-06",
messages=[
{
"role": "system",
"content": """You are a Named Entity Recognition (NER) assistant.
Your job is to identify and return all entity names and their
types for a given piece of text. You are to strictly conform
only to the following entity types: Person, Location, Organization
and DateTime. If uncertain about entity type, please ignore it.
Be careful of certain acronyms, such as role titles "CEO", "CTO",
"VP", etc - these are to be ignore.""",
},
{
"role": "user",
"content": s
}
],
response_format={
"type": "json_schema",
"json_schema": json_schema,
}
)
输出应该看起来像这样:
{ 'entities': [ {'name': 'Samuel Harris Altman', 'type': 'Person'},
{'name': 'April 22, 1985', 'type': 'DateTime'},
{'name': 'American', 'type': 'Location'},
{'name': 'OpenAI', 'type': 'Organization'},
{'name': '2019', 'type': 'DateTime'},
{'name': 'November 2023', 'type': 'DateTime'}]}
本文中使用的完整源代码可以在这里找到。
它是如何工作的
魔力在于约束采样和无上下文语法(CFG)的结合。我们之前提到过,大多数预训练数据是“自然语言”。从统计学角度来看,这意味着在每一个解码/采样步骤中,从已学习的词汇表中采样某个任意令牌的概率是不可忽略的(在现代 LLM 中,词汇表通常包含超过 40,000 个令牌)。然而,在处理正式模式时,我们确实希望快速排除所有不太可能的令牌。
在前面的例子中,如果我们已经生成了…
{ 'entities': [ {'name': 'Samuel Harris Altman',
…那么理想情况下,我们希望在下一步解码时对'typ
令牌施加一个非常高的 logit 偏置,并对词汇表中的所有其他令牌施加非常低的概率。
本质上,发生的就是这个过程。当我们提供模式时,它会被转换成一种形式化的文法,或称上下文无关文法(CFG),该文法在解码步骤中用于引导 logit 偏置值。CFG 是一种经典的计算机科学和自然语言处理(NLP)机制,现在正重新崛起。实际上,这个 StackOverflow 回答对 CFG 做了一个非常好的介绍,但本质上,它是一种描述符号集合转换规则的方式。
结论
结构化输出并不是什么新鲜事物,但随着专有 API 和大型语言模型(LLM)服务的发展,它们无疑已成为关注的重点。结构化输出在 LLM 的“不稳定”和“不可预测”的“自然语言”领域与软件工程的确定性和结构化领域之间架起了一座桥梁。对于任何设计复杂 LLM 应用程序的人来说,结构化输出本质上是必须的,尤其是在 LLM 输出必须以各种组件共享或“展示”的情况下。尽管 API 本地支持终于到来,但构建者仍应考虑使用如 Outlines 等库,因为它们提供了一种与 LLM/API 无关的方式来处理结构化输出。
结构化状态空间模型视觉解析
🐍 向 Mamba 状态空间模型迈进:图像、视频和时间序列
第二部分 — 向 Mamba 状态空间模型迈进:图像、视频和时间序列
·发布于 Towards Data Science ·17 分钟阅读·2024 年 8 月 22 日
--
图片来源:Sascha Kirch.
这是我的新多部分系列的第二部分:🐍 向 Mamba 状态空间模型迈进:图像、视频和时间序列.
状态空间模型,几十年来在许多工程学科中得到应用,现在也开始在深度学习中登场。在我们迈向 Mamba 选择性状态空间模型及其最新研究成果的过程中,理解状态空间模型至关重要。而且,正如在工程中常见的那样,正是这些细节让理论概念在实践中变得可行。除了状态空间模型之外,我们还必须讨论如何将它们应用于序列数据、如何处理长程依赖关系,以及如何通过利用某些矩阵结构高效地训练它们。
结构化状态空间模型为 Mamba 奠定了理论基础。然而,它们与系统理论和高级代数的关联可能是采用这一新框架的障碍之一。
所以,让我们一步一步解析,确保我们理解关键概念,并通过可视化帮助阐明这项新旧理论。
使用 Overpass API 提取地铁路线数据:一步步指南
通过 Overpass API 简化 OpenStreetMaps 的地理数据提取
·发表于 Towards Data Science ·10 分钟阅读·2024 年 9 月 3 日
--
汉堡地铁网络的 Folium 可视化(图片由作者制作)
OpenStreetMaps 是最重要的地理信息数据源之一。平台上可用的许多数据能够帮助我们进行广泛的分析,但我们该如何轻松下载数据以进行分析呢?Overpass API 允许通过定制查询访问平台上所有可用的数据。这个 API 是流行的 Python 库 OSMnx 的基础,凭借其个性化查询,它让我们获得的数据比 Python 库更多,因为后者仅限于从 OpenStreetMaps 提取的最常见数据。
在本文中,我们将使用 API 获取位于汉堡的地铁路线。利用这些路线,我们将创建一个 NetworkX 图,并通过 Folium 中的交互式可视化展示它。本文提取的数据可以用于多种分析,例如评估不同家庭到地铁站的距离,以预测它们的货币价值。
正如我们所见,地理数据对广泛的分析具有极高的价值。因此,掌握轻松提取这些数据的工具是非常必要的。让我们开始阅读本文吧!
成功的人工智能伦理与治理:弥合解释鸿沟
一般化的原则需要专业人员来执行
·发布于 Towards Data Science ·5 分钟阅读·2024 年 10 月 24 日
--
图片来源:Una Laurencic
人工智能伦理与治理已成为一个喧嚣的领域。
根据最新统计,OECD 追踪器显示,截至 2024 年 9 月,已有超过 1,800 个国家级文件涉及有关倡议、政策、框架和战略(而且似乎每一项都有人在发表评论)。
然而,正如 Mittelstadt (2021) 简明扼要地指出的,只有原则本身无法保证伦理人工智能的实现。
尽管有大量高层次的指导方针,政策与现实世界的实施之间依然存在明显的差距。那么,为什么会出现这种情况,数据科学和人工智能领域的领导者应如何思考这一问题呢?
在本系列文章中,我旨在通过将这一差距分解为三个组成部分,并结合研究和实际经验,提出在大规模实施人工智能伦理和治理能力时行之有效的策略和结构,以推进组织内人工智能伦理与治理的成熟度。
我所讨论的第一个差距是解释鸿沟,它来源于将模糊语言表达的原则(例如“以人为本”等)应用到实际中的挑战……
成功的人工智能伦理与治理规模化:弥合组织和实施的鸿沟
从伦理到高管的道路蜿蜒穿过律师和法律硕士(LLM)
·发布于Towards Data Science ·9 分钟阅读·2024 年 11 月 14 日
--
设计者:Freepik
在人工智能治理方面,全球最大的人工智能公司、最聪明的人工智能学者和最著名的咨询公司有哪些共同点?
他们都没有负责在你的公司内实际实现这些目标。
这是关于在大型组织中成功实施人工智能伦理与治理的系列文章的第二部分。第一部分讨论了解释的挑战:即需要专业人才来弥合高层政策与独特人工智能应用案例之间的鸿沟。
本文讨论了接下来的两个鸿沟:组织鸿沟,即人工智能伦理和治理的所有权在不同部门之间的挑战;以及实施鸿沟,即在推动采用人工智能的压力下,对实施大规模人工智能伦理和治理措施的抗拒。
重点在于人工智能伦理和治理的规模化——以一种嵌入公司核心流程和决策的方式进行。开始进行人工智能伦理工作很容易——问题是它们通常以三大 P(原则、试点和公关)收场。Munn(2022 年)提出的具有挑战性的论文《The...
通过数据视角看夏季奥运会
使用 Python 和维基百科绘制获奖国家的地理和网络地图。
·发布于 Towards Data Science ·阅读时间:13 分钟·2024 年 7 月 23 日
--
今年的夏季奥运会将在巴黎举办,几天后即将开始,因此我决定作为一名数据科学家,深入研究奥运会的历史,尽管我对奥运体育项目并没有过多关注。
换句话说,我想要通过依赖公开的维基百科数据,弄清楚哪些国家在奥运会上曾经是最耀眼的明星,以及各国之间的主要竞争对手是谁。具体来说,我收集了每个国家赢得的金牌、银牌和铜牌的总数,并细分到各个单项体育项目。然后,我将这些总奖牌数展示在全球地图上,利用奖牌-体育项目的资料构建并可视化一个类似的国家网络,展示具有相似体育项目配置的国家竞争集群。
让我们从构建数据集开始吧!
所有图片均由作者创作。
1. 数据库
1. 1. 获取完整的奥运参赛国家名单
首先,我访问了维基百科上的历届奥运会奖牌榜页面,它简要总结了……
使用 DSPy 和 Langfuse 提升你的 LLM 应用程序
轻松构建生产级 LLM 应用
·发表于Towards Data Science ·阅读时间 12 分钟·2024 年 10 月 7 日
--
图片来源:Glen Carrie 在Unsplash
LLM 的崛起
大型语言模型(LLM)已成为一种变革性力量,彻底改变了我们与信息的互动和处理方式。这些强大的人工智能模型能够理解和生成类似人类的文本,已经在多个领域找到了应用,从聊天机器人和虚拟助手到内容创作和数据分析。
常规的基于提示的开发工作流。来源:作者
然而,构建和维护高效的 LLM 驱动应用程序并非没有挑战。提示工程,即为 LLM 制定精确指令的艺术,可能是一个耗时且反复迭代的过程。由于这些模型具有固有的“黑箱”特性,调试和故障排除 LLM 的行为也可能变得复杂。此外,了解 LLM 应用程序的性能和成本影响对于优化和可扩展性至关重要(这是任何生产环境设置的关键组件)。
LLM 生态系统
LLM 的生态系统仍处于初期阶段。为了解决其中的一些挑战,许多创新工具和框架正在开发中。来自斯坦福大学的DSPy就是一种正式化 LLM 应用开发的独特尝试。另一方面,Langfuse则作为一个解决方案出现,旨在简化和操作化 LLM 应用维护的各个方面。简而言之:
-
DSPY提供了一个模块化和可组合的框架,用于构建 LLM 应用程序,抽象化了提示工程的复杂性,使开发人员能够专注于应用程序的核心逻辑。
-
Langfuse提供了一个全面的可观测性平台,帮助 LLM 应用程序提供关于模型性能、成本和用户交互的深刻洞察。
通过结合 DSPy 和 Langfuse,开发人员可以释放 LLM 的全部潜力,构建强大、可扩展且具有深刻洞察力的应用程序,从而提供卓越的用户体验。
利用 DSPy 释放大语言模型(LLM)的潜力
语言模型是极其复杂的机器,能够从一个非常大的潜在空间中检索和重组信息。为了引导这个搜索并获得期望的响应,我们在很大程度上依赖复杂、冗长且脆弱的提示(有时这些提示对于特定的 LLM 来说非常具体)。
作为一个开放的研究领域,各个团队从不同的角度工作,以抽象化和加速 LLM 系统的快速开发。DSPy 就是一个针对 LLM 提示和权重进行算法优化的框架。
好吧,你让我感兴趣了,能告诉我更多吗?
DSPy 框架从深度学习框架(如PyTorch)中汲取灵感。
例如,使用 PyTorch 构建深度神经网络时,我们只需使用标准层,如卷积、丢弃、线性,并将其与优化器(如Adam)连接,然后进行训练,而不必每次都从头开始实现这些。
同样,DSPy 提供了一套标准的通用模块(例如ChainOfThought、Predict)、优化器(例如BootstrapFewShotWithRandomSearch),并通过将这些组件作为层组合成一个程序来帮助我们构建系统,而无需显式处理提示!是不是很棒?
DSPy 构建块与工作流
图 1:(左)DSPy 构建块,包括签名(Signatures)、模块(Modules)、优化器(Optimizers)。(右) DSPy 程序工作流。来源:作者
如图 1所示,DSPy 是一个类似 PyTorch/乐高积木的框架,用于构建基于 LLM 的应用程序。开箱即用,它包括:
-
签名:这些是用于定义 DSPy 程序输入输出行为的规范。可以使用简写表示法(例如,“question -> answer”,框架会自动理解 question 是输入,answer 是输出),或者使用声明式规范,通过 Python 类进行定义(后续章节将详细介绍)。
-
模块:这些是预定义组件的层,用于实现强大的概念,如Chain of Thought、ReAct,甚至是简单的文本完成(Predict)。这些模块抽象了底层脆弱的提示,同时仍然通过自定义组件提供可扩展性。
-
优化器:这些优化器是 DSPy 框架特有的,灵感来源于 PyTorch 本身。这些优化器利用注释数据集和评估指标,帮助调优/优化基于 LLM 的 DSPy 程序。
-
数据、指标、断言和跟踪器是该框架的其他组成部分,它们像粘合剂一样在背后工作,丰富了整体框架。
要使用 DSPy 构建应用/程序,我们采用模块化且循序渐进的方法(如图 1(右)所示)。我们首先定义我们的任务,以帮助我们清晰地定义程序的签名(输入和输出规范)。接下来,我们构建一个管道程序,该程序利用一个或多个抽象化的提示模块、语言模型模块以及检索模型模块。完成这些步骤后,我们接着准备一些示例,并结合所需的指标来评估我们的设置,这些指标被优化器和断言组件用来编译一个强大的应用。
使用 Langfuse 获取 LLM 洞察
Langfuse 是一个 LLM 工程平台,旨在帮助开发者构建、管理和优化基于 LLM 的应用。虽然它提供了托管和自托管解决方案,但在本文中我们将重点介绍自托管选项,使你能够完全控制自己的 LLM 基础设施。
Langfuse 设置的主要亮点
Langfuse 为你提供了一套强大的工具,简化了 LLM 开发流程:
-
提示管理: 轻松版本控制和检索提示,确保可重复性并促进实验。
-
追踪: 通过详细的追踪信息深入了解你的 LLM 应用,便于高效调试和故障排除。开箱即用的直观 UI 使团队能够标注模型交互,以开发和评估训练数据集。
-
指标: 跟踪重要指标,如成本、延迟和令牌使用情况,使你能够优化性能并控制开销。
-
评估: 捕获用户反馈,注释 LLM 响应,甚至设置评估函数,持续评估并改进你的模型。
-
数据集: 管理和组织来自 LLM 应用的数据集,促进进一步的微调和模型增强。
轻松设置
Langfuse 的自托管解决方案非常易于设置,利用基于docker的架构,你可以通过docker compose快速启动。这种简化的方式最小化了部署复杂性,使你能够专注于构建你的 LLM 应用。
框架兼容性
Langfuse 与流行的 LLM 框架如LangChain、LlamaIndex以及 DSPy 无缝集成,使其成为适用于各种 LLM 开发框架的多功能工具。
DSPY + Langfuse 的强大功能
通过将 Langfuse 集成到你的 DSPy 应用中,你可以解锁丰富的可观察性功能,使你能够实时监控、分析和优化你的模型。
将 Langfuse 集成到你的 DSPy 应用中
集成过程简单,涉及使用 Langfuse 的 SDK 为你的 DSPy 代码添加监控功能。
import dspy
from dsp.trackers.langfuse_tracker import LangfuseTracker
# configure tracker
langfuse = LangfuseTracker()
# instantiate openai client
openai = dspy.OpenAI(
model='gpt-4o-mini',
temperature=0.5,
max_tokens=1500
)
# dspy predict supercharged with automatic langfuse trackers
openai("What is DSPy?")
通过 Langfuse 获得洞察
一旦集成,Langfuse 会提供一系列关于 DSPy 应用行为的可操作洞察:
-
基于追踪的调试: 跟踪 DSPY 程序的执行流程,定位瓶颈,并识别需要改进的地方。
-
性能监控: 跟踪关键指标,如延迟和令牌使用量,以确保最佳的性能和成本效益。
-
用户互动分析: 了解用户如何与 LLM 应用互动,识别常见查询,并发现优化机会。
-
数据收集与微调: 收集并注解 LLM 的响应,构建有价值的数据集,以便进一步的微调和模型优化。
用例增强
DSPy 和 Langfuse 的结合在以下场景中尤为重要:
-
复杂的管道: 在处理涉及多个模块的复杂 DSPy 管道时,Langfuse 的追踪功能对调试和理解信息流至关重要。
-
生产环境: 在生产环境中,Langfuse 的监控功能确保你的 LLM 应用顺利运行,提前警告潜在问题,同时关注相关成本。
-
迭代开发: Langfuse 的评估和数据集管理工具促进了基于数据的迭代,允许你根据真实世界的使用情况不断优化你的 LLM 应用。
元用例:我的工作坊问答机器人
为了真正展示 DSPy 与 Langfuse 强大监控能力的结合,我最近将它们应用于一个独特的数据集:我最近的 LLM 工作坊 GitHub 仓库。这个为期一天的工作坊包含了大量的材料,帮助你入门 LLM。这个问答机器人旨在帮助参与者在工作坊期间及其后,解答一系列与 NLP 和 LLM 相关的主题。这个“元”用例不仅展示了这些工具的实际应用,还为我们的探索增添了一些自我反思的色彩。
任务:构建问答系统
在本次练习中,我们将利用 DSPy 构建一个问答系统,该系统能够回答关于我的工作坊内容(笔记本、markdown 文件等)的问题。这个任务突出了 DSPy 处理和提取文本数据中信息的能力,这是许多 LLM 应用中至关重要的功能。试想一下,拥有一个个人 AI 助手(或副驾驶),能够帮助你回忆过去几周的细节、识别工作中的模式,甚至挖掘被遗忘的洞察!这还展示了这样的模块化设置如何轻松扩展到任何其他文本数据集,几乎不需要任何努力。
让我们从设置程序所需的对象开始。
import os
import dspy
from dsp.trackers.langfuse_tracker import LangfuseTracker
config = {
'LANGFUSE_PUBLIC_KEY': 'XXXXXX',
'LANGFUSE_SECRET_KEY': 'XXXXXX',
'LANGFUSE_HOST': 'http://localhost:3000',
'OPENAI_API_KEY': 'XXXXXX',
'OPENAI_BASE_URL': 'XXXXXX',
'OPENAI_PROVIDER': 'XXXXXX',
'CHROMA_DB_PATH': './chromadb/',
'CHROMA_COLLECTION_NAME':"supercharged_workshop_collection",
'CHROMA_EMB_MODEL': 'all-MiniLM-L6-v2'
}
# setting config
os.environ["LANGFUSE_PUBLIC_KEY"] = config.get('LANGFUSE_PUBLIC_KEY')
os.environ["LANGFUSE_SECRET_KEY"] = config.get('LANGFUSE_SECRET_KEY')
os.environ["LANGFUSE_HOST"] = config.get('LANGFUSE_HOST')
os.environ["OPENAI_API_KEY"] = config.get('OPENAI_API_KEY')
# setup Langfuse tracker
langfuse_tracker = LangfuseTracker(session_id='supercharger001')
# instantiate language-model for DSPY
llm_model = dspy.OpenAI(
api_key=config.get('OPENAI_API_KEY'),
model='gpt-4o-mini'
)
# instantiate chromadb client
chroma_emb_fn = embedding_functions.\
SentenceTransformerEmbeddingFunction(
model_name=config.get(
'CHROMA_EMB_MODEL'
)
)
client = chromadb.HttpClient()
# setup chromadb collection
collection = client.create_collection(
config.get('CHROMA_COLLECTION_NAME'),
embedding_function=chroma_emb_fn,
metadata={"hnsw:space": "cosine"}
)
一旦我们设置好了这些客户端和跟踪器,就让我们快速地向我们的文档库中添加一些文档(有关我如何准备这个数据集的详细操作,请参阅此笔记本)。
# Add to collection
collection.add(
documents=[v for _,v in nb_scraper.notebook_md_dict.items()],
ids=doc_ids, # must be unique for each doc
)
下一步是简单地将我们的 chromadb 检索器连接到 DSPy 框架。以下代码片段创建了一个 RM 对象,并测试检索是否按预期工作。
retriever_model = ChromadbRM(
config.get('CHROMA_COLLECTION_NAME'),
config.get('CHROMA_DB_PATH'),
embedding_function=chroma_emb_fn,
client=client,
k=5
)
# Test Retrieval
results = retriever_model("RLHF")
for result in results:
display(Markdown(f"__Document__::{result.long_text[:100]}... \n"))
display(Markdown(f">- __Document id__::{result.id} \n>- __Document score__::{result.score}"))
结果看起来很有前景,因为在没有任何干预的情况下,Chromadb 能够获取最相关的文档。
Document::# Quick Overview of RLFH
The performance of Language Models until GPT-3 was kind of amazing as-is. ...
- Document id::6_module_03_03_RLHF_phi2
- Document score::0.6174977412306334
Document::# Getting Started : Text Representation Image
The NLP domain ...
- Document id::2_module_01_02_getting_started
- Document score::0.8062083377747705
Document::# Text Generation <a target="_blank" href="https://colab.research.google.com/github/raghavbali/llm_w" > ...
- Document id::3_module_02_02_simple_text_generator
- Document score::0.8826038964887366
Document::# Image DSPy: Beyond Prompting
<img src= "./assets/dspy_b" > ...
- Document id::12_module_04_05_dspy_demo
- Document score::0.9200280698248913
最后一步是将所有这些内容整合在一起,准备一个 DSPy 程序。对于我们的简单问答用例,我们准备了一个标准的 RAG 程序,利用 Chromadb 作为检索器,Langfuse 作为跟踪器。以下代码片段展示了开发基于 LLM 的应用程序的类似 pytorch 的方法,无需担心脆弱的提示!
# RAG Signature
class GenerateAnswer(dspy.Signature):
"""Answer questions with short factoid answers."""
context = dspy.InputField(desc="may contain relevant facts")
question = dspy.InputField()
answer = dspy.OutputField(desc="often less than 50 words")
# RAG Program
class RAG(dspy.Module):
def __init__(self, num_passages=3):
super().__init__()
self.retrieve = dspy.Retrieve(k=num_passages)
self.generate_answer = dspy.ChainOfThought(GenerateAnswer)
def forward(self, question):
context = self.retrieve(question).passages
prediction = self.generate_answer(context=context, question=question)
return dspy.Prediction(context=context, answer=prediction.answer)
# compile a RAG
# note: we are not using any optimizers for this example
compiled_rag = RAG()
呼!这不是很简单快速吗?现在让我们通过几个示例问题来实际应用它。
my_questions = [
"List the models covered in module03",
"Brief summary of module02",
"What is LLaMA?"
]
for question in my_questions:
# Get the prediction. This contains `pred.context` and `pred.answer`.
pred = compiled_rag(question)
display(Markdown(f"__Question__: {question}"))
display(Markdown(f"__Predicted Answer__: _{pred.answer}_"))
display(Markdown("__Retrieved Contexts (truncated):__"))
for idx,cont in enumerate(pred.context):
print(f"{idx+1}. {cont[:200]}..." )
print()
display(Markdown('---'))
输出确实非常准确,达到了作为此工作坊材料助手的目的,能够回答问题并很好地引导参与者。
图 2:DSPy RAG 程序的输出。来源:作者
Langfuse 的优势
在本文的前面部分,我们讨论了 langfuse 是如何通过让我们监控 LLM 的使用情况并改进管道的其他方面来完善整个流程的。langfuse 作为跟踪工具的惊人集成,通过一个简单易用的界面将所有内容在后台串联起来。在当前的设置中,langfuse 仪表板呈现了我们 LLM 使用情况的快速总结。
图 3:Langfuse 仪表板。来源:作者
仪表板包括如追踪数量、总成本甚至令牌使用等指标(这些在优化管道时非常有用)。
洞察与好处
Langfuse 的实用性不仅仅局限于顶层的度量仪表板。它还提供了追踪级别的详细信息(如图 4所示)。
图 4:Langfuse 跟踪详细信息,包括成本、令牌使用、提示以及模型响应。来源:作者。
这个界面是通往多个其他方面的门户,这些方面在迭代和改进基于 LLM 的应用程序时非常有用。首先要提到的功能是基于实际使用情况准备数据集。这些数据集可用于微调 LLM、优化 DSPy 程序等。图 5 展示了如何从网页 UI 本身轻松定义数据集,并根据需要将跟踪信息(输入请求及模型响应)添加到数据集中。
图 5:(左)通过提供数据集名称和描述等必要信息,直接从网页 UI 创建一个新的数据集。(右)可以通过点击按钮将跟踪记录添加到数据集中。来源:作者
类似于数据集创建和向其中添加数据点,langfuse 简化了指标的创建和数据点的标注。图 6 展示了只需点击几下按钮,便能轻松完成相同操作。
图 6:Langfuse 中的指标创建与标注。来源:作者
一旦我们准备好数据集,langfuse 提供了一个简单的 SDK,让你在你喜欢的编程语言中使用它。以下代码片段使用 langfuse 的 get_dataset 工具,从我们添加到示例数据集中的几个跟踪记录中获取数据。然后,我们仅通过一行代码修改,便能用 LLaMA 3.1 来驱动我们的 DSPy RAG 程序(说到模块化 😉)。
# get annotated dataset
annotated_dataset = langfuse.get_dataset("llm_workshop_rag")
# ensure ollama is available in your environment
ollama_dspy = dspy.OllamaLocal(model='llama3.1',temperature=0.5)
# get langfuse client from the dspy tracker object
langfuse =langfuse_tracker.langfuse
# Set up the ollama as LM and RM
dspy.settings.configure(lm=ollama_dspy,rm=retriever_model)
# test rag using ollama
ollama_rag = RAG()
# iterate through samples from the annotated dataset
for item in annotated_dataset.items:
question = item.input[0]['content'].split('Question: ')[-1].split('\n')[0]
answer = item.expected_output['content'].split('Answer: ')[-1]
o_pred = ollama_rag(question)
# add observations to dataset related experiments
with item.observe(
run_name='ollama_experiment',
run_description='compare LLaMA3.1 RAG vs GPT4o-mini RAG ',
run_metadata={"model": "llama3.1"},
) as trace_id:
langfuse.score(
name="visual-eval",
# any float value
value=1.0,
comment="LLaMA3.1 is very verbose",
)
# attach trace with new run
langfuse.trace(input=question,output=o_pred.answer,metadata={'model':'LLaMA3.1'})
display(Markdown(f"__Question__: {question}"))
display(Markdown(f"__Predicted Answer (LLaMA 3.1)__: {o_pred.answer}"))
display(Markdown(f">__Annotated Answer (GPT-4o-mini)__: _{answer}_"))
如上面的代码片段所示,我们只需遍历数据集中的数据点,直观地比较两个模型的输出(参见 图 7)。使用 Langfuse SDK,我们可以轻松地将实验观察结果、新的跟踪记录和评估分数附加上去。
图 7:使用 Langfuse 准备的数据集中的数据点为 LLaMA3.1 驱动的 RAG 提供输出
图 7 中展示的输出清晰地显示了 LLaMA3.1 驱动的 RAG 确实能回答问题,但在简洁性方面偏离了指令。这可以通过 DSPy 断言轻松捕捉到,同时可以使用 langfuse SDK 跟踪分数,以便进行进一步改进。
结论
在这个快速发展的 LLM 应用领域,像 DSPy 和 Langfuse 这样的工具成为开发者和数据科学家的宝贵助手。DSPy 简化了开发过程,使你能够轻松高效地构建复杂的 LLM 应用。同时,Langfuse 提供了关键的可观察性层,让你深入了解模型的表现,优化资源利用,并基于真实数据持续改进应用。
DSPY 和 Langfuse 的结合解锁了无限可能,让你能够充分发挥 LLM 的潜力。无论你是在构建问答系统、内容生成器,还是其他任何基于 LLM 的应用,这些工具都为创建强大、可扩展和富有洞察力的解决方案奠定了基础。
正如我在最近的 LLM 工作坊的元使用案例——回答问题——中所展示的,DSPy 和 Langfuse 可以创造性地应用于从你自己的个人数据中提取有价值的洞察。可能性真的无穷无尽。
我鼓励你在自己的项目中探索这些工具/框架。感兴趣的朋友可以通过我的GitHub 仓库获取更多关于其他主题的综合性实践工作坊资料。借助这些工具,你将能够大幅提升你的 LLM 应用,并在快速发展的 AI 领域中保持领先。
免责声明:我与文中提到的任何工具、产品或公司没有任何关联,不论是财务上的还是其他方面的。这些观点和见解仅基于个人经验和独立研究。
参考资料
[## GitHub - raghavbali/llm_workshop: LLM Workshop 2024
LLM Workshop 2024。通过在 GitHub 上创建账户,参与 raghavbali/llm_workshop 项目的开发。
github.com](https://github.com/raghavbali/llm_workshop.git?source=post_page-----f83c02ba96a1--------------------------------)
超级增强的 Pandas:用新颖的方法追踪依赖关系
一种面向对象的方法,用于管理多个文件和数据框,以及追踪依赖关系。
·发表于Towards Data Science ·阅读时长 7 分钟·2024 年 3 月 11 日
--
图片由Sibel Yıldırım提供,来源于Unsplash
这将如何对你有所帮助:
本文描述了一种面向对象的数据分析方法。它概述了两种新颖的方法:(a)通过将数据框分配给Reports
对象的属性,减少重复的文件读取,和(b)递归地追踪依赖方法以构建属性。这些方法让我在工作中非常高效,希望你也能获得类似的好处。
适合谁阅读:
-
你需要长时间分析相同的数据集。
-
你需要通过将不同来源的数据结合起来建立报告并准备统计数据。
-
你的同事们经常问你:“你是如何得到这些数据的?”而你无法回忆起你在 Excel 中为了准备报告而做的 N 步操作。
-
你已经使用 pandas 一段时间了,并且怀疑有一种更高效的做事方式。
本文内容包括:
-
单体脚本:它是如何开始的
-
可复用的函数:它是如何发展的
-
对象、方法和属性:它是如何发展的
-
追踪上游依赖关系:一种新颖的方法
前言
我尝试做的事情很难解释,所以如果本文的前半部分不太通顺,请耐心等待。我保证,到了后面,所有的内容都会变得值得。
单体脚本:它是如何开始的
假设你有 3 个 csv 文件:file1.csv
、file2.csv
、file3.csv
。你编写了一些代码来读取它们中的每一个,然后按特定顺序将它们合并。
df1 = pd.read_csv('file1.csv')
df2 = pd.read_csv('file2.csv')
df3 = pd.read_csv('file3.csv')
df1_2 = pd.merge(df1, df2, on='a', how='left')
df1_2_3 = pd.merge(df1_2, df3, on='b', how='inner')
这非常完美,你继续着自己的工作。接着,老板给了你file4.csv
,这个文件需要与file1.csv
合并,生成一个单独的报告。没问题,你知道该怎么做,更新了代码:
df1 = pd.read_csv('file1.csv')
df2 = pd.read_csv('file2.csv')
df3 = pd.read_csv('file3.csv')
df4 = pd.read_csv('file4.csv')
df1_2 = pd.merge(df1, df2, on='a', how='left')
df1_2_3 = pd.merge(df1_2, df3, on='b', how='inner')
df1_4 = pd.merge(df1, df4, on='a', how='left')
代码顺利运行,你得到了预期的输出。老板拍了拍你的背,开玩笑地说:“这么快,你能更快一点吗?”
你抬头看着老板,但眼前只看见一连串的脏话在闪过。你努力控制自己内心的冲动,决定不让自己爆发出来,而是战胜了本能,聚集起所有的伪善能量,假装微笑并愉快地回答:“当然,给我个机会。”
随着一阵脏话渐行渐远,随着你耗尽所有的能量,你注意到一线希望:如果你只对df1_4
感兴趣,那么就不需要读取file2.csv
和file3.csv
。你突然意识到,这种对宝贵时间和计算能力的挥霍,与自己对可持续性的承诺相悖,开始思考如何通过只读取必要的数据来提高代码的效率。
可复用的函数:它是如何发展的
你回忆起几年前参加的编程课程,并开始编写一些函数:
files = {1: 'file1.csv', 2: 'file2.csv', 3:'file3.csv', 4:'file4.csv'}
def get_df(x):
return pd.read_csv(files[x])
def get_df1_2():
df1 = get_df(1)
df2 = get_df(2)
return pd.merge(df1, df2, on='a', how='left')
def get_df1_2_3():
df1_2 = get_df1_2()
df3 = get_df(3)
return pd.merge(df1_2, df3, on='b', how='inner')
def get_df1_4():
df1 = get_df(1)
df4 = get_df(4)
return pd.merge(df1, df4, on='a', how='left')
你为自己感到高兴。虽然代码行数已经翻倍,但你安慰自己,从长远来看,这样更易于管理。此外,你为这种方法辩护,因为你可以获取特定的输出数据框,每个数据框只会读取所需的表格,其他的什么都不读。你感到一阵寒意袭来,内心的声音开始质疑你的思维。“你确定吗?”它用命令的语气大声质问道,听起来像个军训教官。空气中弥漫着一阵沉默,除了你脑海中想象中的齿轮转动声,什么都听不见……突然,你的眼睛一亮,意识到,如果你需要df1_2
和df1_4
,那么file1.csv
将被读取两次!轰!
对象、方法和属性:它是如何演变的
再一次,你回忆起大学里的编程课程,记起你可以通过创建一个Reports
对象来解决这个问题。当读取完一个数据框后,可以将其设置为Reports
对象的一个属性,以便后续访问。
files = {1: 'file1.csv', 2: 'file2.csv', 3:'file3.csv', 4:'file4.csv'}
class Reports:
def __init__(self):
self.df1 = pd.read_csv(files[1])
def get_df1_2(self):
self.df2 = pd.read_csv(files[2])
self.df1_2 = pd.merge(self.df1, self.df2, on='a', how='left')
return self.df1_2
def get_df1_4(self):
self.df4 = pd.read_csv(files[4])
self.df1_4 = pd.merge(self.df1, self.df4, on='a', how='left')
def get_df1_2_3(self):
self.get_df1_2()
self.df3 = pd.read_csv(files[3])
self.df1_2_3 = pd.merge(self.df1_2, self.df3, on='b', how='inner')
哇!你解决了多次读取同一文件的问题。但还有另一个问题:get_df1_2_3
如果需要经过许多步骤(例如过滤、选择、布尔掩码、去重等),可能会变得非常复杂。
追踪上游依赖:一种新方法
你深深地吸了一口气,心想……代码能否搞明白,如果self.df1_2
没有被设置,那么它应该调用self.get_df1_2()
?更普遍地说,当访问的属性不存在时,能否识别出哪个方法负责设置它,然后调用这个方法?如果能做到这一点,那么你可以通过x=Reports(); x.df1_2_3
在一个命令中得到所需的数据框。
这难道不值得为之奋斗吗?这难道不值得为之牺牲吗?——摩菲斯,《黑客帝国:重装上阵》,2003 年
就像一个疯狂的科学家在工作,你开始猛击键盘,偶尔抬头,用手指在空中画出编程抽象图,并将它们连接起来。从眼角的余光中,你注意到一个你从未见过的同事露出困惑的表情——或者可能是厌恶的表情,但你无法确定。你把所有的注意力都集中在进入心流状态,完全忽视了周围发生的一切。大楼可能着火了,但只要你可靠的 Notepad++继续显示你输入的每一个键,你根本不会察觉。
files = {'df1': 'file1.csv', 'df2': 'file2.csv',
'df3': 'file3.csv', 'df4': 'file4.csv'}
class Reports:
def __init__(self):
self._build_shortcuts()
def _read(self, k):
setattr(self, k, pd.read_csv(files[k]))
def _build_shortcuts(self):
# Dict: Method -> list of attributes
dict0 = {'get_df1_2': ['df1_2'],
'get_df1_4': ['df1_4'],
'get_df1_2_3': ['df1_2_3']}
# Dict: Attribute -> method which creates the attribute
dict1 = {v:k for k, values in dict0.items() for v in values}
self._shortcuts = dict1
def __getattr__(self, attr):
if not attr in self.__dict__: # if the attr has not been created...
if attr in self._shortcuts:
func = self._shortcuts[attr]
# `func` is the method responsible for creating attr
self.__getattribute__(func)()
return self.__getattribute__(attr)
elif attr in files:
self._read(attr)
return self.__getattribute__(attr)
else:
raise AttributeError
else:
return self.__getattribute__(attr)
def get_df1_2(self):
self.df1_2 = pd.merge(self.df1, self.df2, on='a', how='left')
return self.df1_2
def get_df1_4(self):
self.df1_4 = pd.merge(self.df1, self.df4, on='a', how='left')
return self.df1_4
def get_df1_2_3(self):
self.df1_2_3 = pd.merge(self.df1_2, self.df3, on='b', how='inner')
return self.df1_2_3
你停下来片刻,欣赏自己的创作,它的优雅与简洁。在短短的一瞬间,你想象着这将如何造福程序员和数据分析师。当你在愉悦的热气球上飞翔时,一个内心的声音像镣铐一样降临在你身上。“保持脚踏实地,”他说,“因为你可能不是第一个想到这个主意的人。”你收拾心情,开始记录你的工作,意识到几天后你可能会不明白自己写的是什么。
__init__()
不读取文件。它只是调用 build_shortcuts()
。
-
_build_shortcuts()
和__getattr__
密切配合,简化了后续方法中的代码。 -
_build_shortcuts()
接受一个字典,字典的键是方法,值是属性的列表,然后将其反转,形成一个字典,字典的键是属性,值是设置属性的方法。 -
__getattr__
做了不少“魔法”。当调用self.<attr>
时,如果attr
不在self.__dict__
中,但在self._shortcuts
中,那么它会识别出负责创建self.<attr>
的方法并调用它。每当你创建一个新方法,如果它设置了一个新属性,那么你只需在self._build_shortcuts()
中更新dict0
。如果它在files
字典的键中,那么它会读取相应的文件并将键设置为Reports
对象的属性。 -
在没有显式编写循环或递归的情况下,
__getattr__
和self._shortcuts
协同工作,追踪上游依赖关系!
目前,这种方法有以下几个优点:
-
只有在绝对需要时,才会读取文件,浪费的时间最少。
-
当文件被读取时,它们只会被读取一次,并且数据会写入属性中。
-
当调用一个属性时,如果它没有被创建,系统会寻找负责设置该属性的方法,然后进行设置。
额外的好处
除了可以通过一个命令访问所需的数据框外,你还可以在_build_shortcuts()
中将其他属性1添加到dict0
的值中。
例如,你可能对df1_2
中列a
的唯一值列表感兴趣。只需将其添加到列表中,然后你可以使用x = Reports(); x.unique_values_in_a
。
...
def _build_shortcuts(self):
# Added 'unique_items_in_a' to the first list.
dict0 = {'get_df1_2': ['df1_2', 'unique_values_in_a'],
'get_df1_4': ['df1_4'],
'get_df1_2_3': ['df1_2_3']}
dict1 = {v:k for k, values in dict0.items() for v in values}
self._shortcuts = dict1
...
def get_df1_2(self):
self.df1_2 = pd.merge(self.df1, self.df2, on='a', how='left')
# Added the list of unique values of column 'a'
self.unique_values_in_a = self.df1_2['a'].unique().tolist()
return self.df1_2
结论
这对你意味着什么?
我强烈建议你在下次需要分析涉及多个数据框的数据时尝试这种方法。
-
对于 Python 新手,你只需复制粘贴
Reports
类、__init__
、__getattr__
和_build_shortcuts
方法。显然,你需要编写自己的方法并更新_build_shortcuts
中的dict0
。 -
对于 Python 专家,我很想听听你对我的方法的看法,如果你正在做类似的事情,或做得更好,请分享。
免责声明
本叙述仅用于说明目的,绝不代表我或我所在公司或客户的任何观点或经验。
这是我第一次使用这种写作风格,如果你喜欢它,请通过点赞、关注和订阅表示感谢,谢谢!
1 非常感谢同伟的校对和建议。
通过符号程序搜索超充提示工程
通过自动探索大量提示变体来找到更好的提示
·发表于Towards Data Science ·阅读时间 8 分钟·2024 年 4 月 22 日
--
图片由Icons8 Team提供,来源于Unsplash
不是什么秘密,LLM(大规模语言模型)的成功很大程度上依赖于我们能够为它们提供正确的提示和示例。随着新一代 LLM 变得越来越强大,提示已经变得足够复杂,以至于可以被视为程序本身。这些提示程序与食谱非常相似——它们都有一组需要遵循的指令,并将原材料(无论是数据还是食材)转化为最终的成果。
提示工程类似于改进一个食谱。家庭厨师通常会遵循整体食谱,但会做一些小的修改——例如在意面菜肴中去掉大蒜或加入香菜。DSPy这样的框架在优化上下文示例时遵循了这种总体范式。然而,专业厨师将食谱视为灵感来源,并且经常完全重新解释菜肴的组成部分。例如,他们可能会将意面视为富含淀粉的组成部分,并将其换成新鲜制作的意大利饺子,以达到类似的效果。
是什么让专业级厨师能如此富有创意地工作呢?正是因为他们以抽象的方式思考食谱,就像上面提到的意面例子一样。手动提示工程类似于专业级烹饪。它可以获得令人印象深刻的结果,但需要大量的时间和知识。我们真正想要的是手动提示工程的创造力,但无需付出那么多努力。
抽象提示的强大之处
假设我们想改进一个用于标注发言人回应的提示。我们最终会使用许多不同的输入,但暂时先插入一个具体的例子:
Instructions: Does Speaker 2's answer mean yes or no?
Output labels: no, yes
Input: Speaker 1: "You do this often?" Speaker 2: "It's my first time."
Output:
假设,暂时我们有一个抽象的提示表示,它提取出单独的组件并且易于操作。也许是这样的:
一个简单的分类任务提示,以抽象符号程序表示。图片由作者提供。
通过这个,你可以自动化在提示原型过程中需要进行的大量(半)手动调整。像改写这样的简单编辑只是开始。想试试链式思维推理吗?添加一段话说“让我们一步一步思考”。怎么样更改数据格式为 JSON?只需更改format
参数的属性。你还可以探索
-
从单一示例到批量注释
-
在 RAG 场景中更改你的检索器和排序函数
-
重新排序一些段落
-
压缩指令中的某些部分
-
等等。
本质上,插入你最喜欢的提示工程启发式方法。提示的这种抽象表示方式使我们能够真正发挥创意,并自动探索大量可能的提示空间。那么,我们如何将提示表示为抽象且可修改的程序呢?继续阅读。
将提示转换为抽象程序
“计算机科学中的任何问题都可以通过另一层间接性来解决。”
为了表示抽象提示,首先让我们将其转换为非符号提示程序,通过将它们拆解为单独的组件,并作为 Python 类实现:
class Component:
def __init__(self, **kwargs): pass
class Metaprompt(Component): pass
class Paragraph(Component): pass
class InputData(Component): pass
prompt = Metaprompt(
children=[
Paragraph(text="Instructions: "),
Paragraph(
id="instructions",
text="Does Speaker 2's answer mean yes or no?",
),
Paragraph(id="labels", text="Output labels: yes, no"),
InputData(),
Paragraph(text="Output: "),
]
)
到目前为止,进展不错。这类似于 DSpy 所做的,尽管更加通用,因为我们还表示了提示的内部结构。
接下来,我们将其转换为符号提示程序,以便可以进行任意更改(这也超出了静态 DSPy 程序的范围)。这可以通过pyGlove库完成,这是一个用于符号面向对象编程(SOOP)的库。pyGlove 将 Python 类转化为可操作的符号对象,其属性在实例化后仍然可以完全编辑。
使用 pyGlove,我们只需要添加pg.symbolize
装饰器:
import pyglove as pg
@pg.symbolize
class Component:
def __init__(self, **kwargs): pass
我们现在可以通过一系列说明符查询和修改提示程序,类似于操作 DOM 树。假设我们希望将上面的程序转换成以下内容:
我们希望实现的目标提示程序。图片来源:作者。
注意,我们现在在问“回应是表示肯定吗?”而不是提供肯定和否定的输出标签。要做到这一点,我们需要(i)更改指令文本,和(ii)删除第三个节点。使用 pyGlove,这非常简单:
prompt.rebind({'children[1].text': 'Does the response mean yes?'})
prompt.rebind({'children[2]': pg.MISSING_VALUE})
print(prompt)
输出确认了我们的成功:
Metaprompt(
children = [
0 : Paragraph(
text = 'Instructions: '
),
1 : Paragraph(
id = 'instructions',
text = 'Does the response mean yes?'
),
2 : InputData(),
3 : Paragraph(
text = 'Output: '
)
]
)
瞧!本质上,pyGlove 为我们提供了一种方式,可以像操作源代码一样使用 Python 类(和函数),几乎没有额外的开销。现在我们有了灵活且易于操作的表示方式,让我们开始使用它们吧。
等一下。我们现在可能有了一种表示和修改提示的方法,但我们仍然缺少一个自动优化它们的过程。
一旦厨师理解了食谱的抽象和组成部分,他们就会尝试多种变体,精炼味道、成本或外观,直到感觉合适。要做同样的事情来优化提示抽象,我们需要一个搜索算法、一个目标以及一组标注样本,以便知道我们在取得进展。
听起来需要自己实现很多东西?认识一下SAMMO,一个用于构建和优化符号提示程序的 Python 库。
热身:使用 SAMMO 进行指令调优
为了说明 SAMMO 的核心工作流程,我们现在将展示如何调整上述提示示例中的指令部分。一旦我们完成了这个简单示例,我们就可以准备讨论更高级的应用,比如 RAG 优化或压缩。
关键步骤是
-
定义你的初始提示
-
准备数据——几百个标注样本就足够了。
-
定义目标
-
选择一组变异器
-
运行优化
步骤 1:定义你的初始提示
我们基本上已经做到了这一点。SAMMO 期望一个函数,所以我们必须将其包装在一个函数中。如果你想存储额外的信息,可以将其包装在可调用对象中。我们还会将它包装在一个输出组件中以运行它。
def starting_prompt():
instructions = MetaPrompt(
Paragraph(text="Instructions: "),
Paragraph(
id="instructions",
text="Does Speaker 2's answer mean yes or no?",
),
Paragraph(id="labels", text="Output labels: yes, no"),
InputData(),
Paragraph(text="Output: "),
)
return Output(instructions.with_extractor())
步骤 2:准备你的数据
SAMMO 使用一种简单的数据结构,称为 DataTable,用于将输入与输出(标签)配对。这将帮助我们进行评估和记账。
mydata = DataTable.from_records(
records, # list of {"input": <>, "output": <>}
constants={"instructions": default_instructions},
)
步骤 3:定义目标
我们的目标是优化准确性,所以我们将在下面实现这个目标:
def accuracy(y_true: DataTable, y_pred: DataTable) -> EvaluationScore:
y_true = y_true.outputs.normalized_values()
y_pred = y_pred.outputs.normalized_values()
n_correct = sum([y_p == y_t for y_p, y_t in zip(y_pred, y_true)])
return EvaluationScore(n_correct / len(y_true))
步骤 4:选择一组变异器
在这里,你可以尽情发挥创造力。你可以实现自己的运算符来生成新的提示变体,或者仅仅依赖于 SAMMO 提供的预构建突变运算符。
在下面,我们采用后者,混合使用释义和从一些标注示例中引导指令,实质上是在实现自动提示工程(APE)。
mutation_operators = BagOfMutators(
starting_prompt=StartingPrompt(d_train),
InduceInstructions({"id": "instructions"}, d_train),
Paraphrase({"id": "instructions"}),
)
第五步:运行优化
runner = OpenAIChat(
model_id="gpt-3.5-turbo-16k",
api_config={"api_key": YOUR_KEY},
cache="cache.tsv",
)
prompt_optimizer = BeamSearch(runner, mutation_operators, accuracy, depth=6)
transformed = prompt_optimizer.fit_transform(d_train)
介绍性示例提示实际上取自于BigBench 含义任务,我们将使用它来运行本次实验。如果您使用 100 个样本进行训练和测试,并设置 48 个候选项评估预算,您将看到 SAMMO 将起始提示的准确率从0.56提升到0.77——提高了37.5%。哪些指令效果最好?
...
Paragraph(
"Consider the dialogue, context, and background "
"information provided to determine the most suitable output label",
id="instructions",
)
...
有趣的是,不同的 LLM 偏好不同的指令。如上所见,GPT-3.5 最喜欢通用指令。SAMMO 为 Llama-2 选择的最佳提示,在相同的训练和预算设置下,指令部分使用了一个空字符串:
...
Paragraph(
"",
id="instructions",
)
...
实践操作:RAG 调优
我们现在将展示如何将 RAG 管道转换为符号程序,并使用 SAMMO 进行调优。我们将使用语义解析作为应用任务,在该任务中,我们希望将用户查询转换为领域特定语言(DSL)结构,例如查询某些数据库或调用外部 API。
为了创建起始提示,我们包括了所有操作符的列表,使用基于嵌入的检索器获取五个少样本示例,然后指示 LLM 按与示例相同的格式输出答案。
class RagStartingPrompt:
def __init__(self, dtrain, examples, embedding_runner):
self._examples = examples
self._dtrain = dtrain
self._embedding_runner = embedding_runner
def __call__(self, return_raw=False):
structure = [
Section("Syntax", self._dtrain.constants["list_of_operators"]),
Section(
"Examples",
EmbeddingFewshotExamples(
self._embedding_runner, self._examples, 5
),
),
Section(
"Complete and output in the same format as above",
InputData(),
),
]
instructions = MetaPrompt(
structure,
render_as="markdown",
data_formatter=JSONDataFormatter(),
)
return Output(
instructions.with_extractor(),
on_error="empty_result",
)
现在我们有了一个符号程序,让我们发挥创意。对于突变,我们进行探索
-
不同数量的少样本示例
-
为少样本示例提供不同格式(XML、JSON、逐行格式)
-
是否提供关于 DSL 的额外信息
-
显示输入输出对或输入输出组
使用这些并尝试总共 24 个候选项时,我们可以看到一个明显的趋势。以下是三个不同数据集在四个不同 LLM 上的测试集准确率。在绝大多数情况下,我们可以看到 SAMMO 显著提升了性能,即使对于表现最好的 LLM 也是如此。
即使预算只有 24 个候选项的评估,我们也能在性能上获得显著提升。图片由作者提供。
结论
将提示转换为符号程序是一个非常强大的想法,可以探索大量可能的提示和设置设计空间。就像一位专业厨师解构并重新诠释食谱以创造烹饪创新一样,符号编程让我们能够将同样的创造力和实验精神应用于自动化提示工程。
SAMMO 通过一组突变操作符和搜索例程实现了符号程序搜索。根据经验,这可以在指令调优和 RAG 调优中带来准确率的大幅提升,与后端 LLM 无关。
您可以通过自定义突变操作符扩展 SAMMO,加入您最喜欢的提示工程技巧,或实现超越准确度(例如,成本)的目标。祝您愉快地进行提示创作!
免责声明: 我是 SAMMO 的作者。
资源
-
阅读: SAMMO 用户指南 和 arXiv 上的论文,包含更多细节
叠加现象:为何它使得神经网络难以解释
当特征数大于模型维度时
·发布于 Towards Data Science ·7 分钟阅读·6 天前
--
介绍
如果神经网络的世界能够呈现一对一的关系,那将是理想的:每个神经元只会激活一个且仅一个特征。在这样的世界中,模型的解释将是直观的:这个神经元会对狗耳特征激活,而那个神经元会对汽车轮子激活。不幸的是,实际情况并非如此。实际上,一个具有维度 d 的模型往往需要表示 m 个特征,其中 d < m。这时我们就会观察到叠加现象。
有一个小的备注,关于一个评论:当 d > m 时,叠加现象仍然可能发生,但其解释和机制不同。隐藏层的更高维度为网络提供了更大的能力,但并未消除叠加现象——它只是允许在更大的表示空间内进行共享和独特特征的更丰富组合。
在机器学习的背景下,叠加指的是一个神经元在模型中表示多个重叠的特征,而不是单一的、独特的特征。例如,InceptionV1 包含一个神经元,该神经元同时响应猫脸、汽车前端和猫腿1。这导致了我们所说的在同一神经元或电路中不同特征的激活叠加现象。
叠加现象的存在使得模型的可解释性变得具有挑战性,尤其是在深度学习模型中,隐藏层中的神经元表示的是模式的复杂组合,而不是与简单、直接的特征相关联。
在这篇博客中,我们将通过 Python 的详细实现展示一个简单的叠加现象示例,代码可以在这个notebook中找到。
超级叠加现象的发生:假设
本节的开始,我们将讨论“特征”这一术语。
在表格数据中,定义特征几乎没有歧义。例如,当使用表格数据集预测葡萄酒的质量时,特征可以是酒精百分比、生产年份等。
然而,在处理非表格数据(如图像或文本数据)时,定义特征的特点可能变得复杂。在这些情况下,特征的定义没有普遍公认的标准。从广义上讲,特征可以被认为是输入的任何属性,是大多数人能够识别的。例如,在一个大型语言模型(LLM)中,一个特征可能是某个词是否是法语词。
当特征数量超过模型维度时,就会发生叠加现象。我们声称,叠加现象发生的前提是必须满足两个必要条件:
-
非线性:神经网络通常在每个隐藏层的末端包含非线性激活函数,如 sigmoid 或 ReLU。这些激活函数赋予网络将输入映射到输出的非线性方式,使得网络能够捕捉特征之间更复杂的关系。我们可以想象,如果没有非线性,模型将表现为简单的线性变换,其中特征仍然是线性可分的,无法通过叠加压缩维度。
-
特征稀疏性:特征稀疏性是指只有少数特征为非零值的情况。例如,在语言模型中,许多特征不能同时存在:例如,同一个词不能同时是法语和其他语言。如果所有特征都是密集的,我们可以想象,由于重叠表示,模型解码特征会遇到很大的干扰。
简单示例:不同稀疏度下的线性与非线性
合成数据集
让我们考虑一个有 40 个特征的简单示例,其中每个特征的重要性线性递减:第一个特征的重要性为 1,最后一个特征的重要性为 0.1,其余特征的重要性在这两个值之间均匀分布。
我们然后使用以下代码生成一个合成数据集:
def generate_sythentic_dataset(dim_sample, num_sapmple, sparsity):
"""Generate synthetic dataset according to sparsity"""
dataset=[]
for _ in range(num_sapmple):
x = np.random.uniform(0, 1, n)
mask = np.random.choice([0, 1], size=n, p=[sparsity, 1 - sparsity])
x = x * mask # Apply sparsity
dataset.append(x)
return np.array(dataset)
这个函数创建一个具有给定维度的合成数据集,在我们的例子中是 40 维。对于每个维度,都会从[0, 1]的均匀分布中生成一个随机值。稀疏度参数在 0 和 1 之间变化,控制每个样本中活跃特征的百分比。例如,当稀疏度为 0.8 时,每个样本中的特征有 80%的概率为零。该函数应用掩码矩阵来实现稀疏度设置。
线性和 ReLU 模型
现在我们想探讨基于 ReLU 的神经模型如何导致叠加现象的形成,以及稀疏值如何改变它们的行为。
我们将实验设置如下:我们将 40 维特征压缩到 5 维空间,然后通过逆过程重构向量。观察这些变换的行为时,我们期望看到在每种情况下的叠加现象。
为了做到这一点,我们考虑了两个非常相似的模型:
-
线性模型:一个简单的线性模型,只有 5 个系数。回想一下,我们希望处理 40 个特征——远远超过模型的维度。
-
ReLU 模型:与线性模型几乎相同,但在最后加入了一个 ReLU 激活函数,引入了一层非线性。
这两个模型都是使用 PyTorch 构建的。例如,我们使用以下代码构建 ReLU 模型:
class ReLUModel(nn.Module):
def __init__(self, n, m):
super().__init__()
self.W = nn.Parameter(torch.randn(m, n) * np.sqrt(1 / n))
self.b = nn.Parameter(torch.zeros(n))
def forward(self, x):
h = torch.relu(torch.matmul(x, self.W.T)) # Add ReLU activation: x (batch, n) * W.T (n, m) -> h (batch, m)
x_reconstructed = torch.relu(torch.matmul(h, self.W) + self.b) # Reconstruction with ReLU
return x_reconstructed
根据代码,n 维输入向量 x 通过与一个 m×n 的权重矩阵相乘,被投影到一个低维空间中。然后,我们通过 ReLU 变换将其映射回原始特征空间,并通过偏置向量进行调整,从而重构原始向量。线性模型的结构与此类似,唯一的区别是重构过程仅使用线性变换,而不是 ReLU。我们通过最小化原始特征样本与重构样本之间的均方误差来训练模型,并且该误差会根据特征重要性进行加权。
结果分析
我们使用不同的稀疏值(0.1、0.5 和 0.9,从较少稀疏到最稀疏)训练了这两个模型,并观察到了一些重要的结果。
首先,无论稀疏性水平如何,ReLU 模型都比线性模型“压缩”特征更好:线性模型主要捕捉具有最高特征重要性的特征,ReLU 模型通过叠加的形式聚焦于不太重要的特征——在这种情况下,一个模型维度代表多个特征。让我们通过以下可视化来观察这种现象:对于线性模型,偏置值在前五个特征中最小(如果你不记得了:特征重要性是根据特征顺序定义的线性递减函数)。相反,ReLU 模型的偏置没有显示出这种顺序,且通常会被进一步减少。
作者提供的图像:重构偏置
另一个重要且有趣的结果是:当特征的稀疏性水平较高时,更容易观察到叠加现象。为了对这一现象有一个印象,我们可以可视化矩阵 W^T@W,其中 W 是模型中的 m×n 权重矩阵。可以将矩阵 W^T@W 解释为输入特征如何投影到低维空间的数量:
特别地:
-
W^T@W 的对角线表示低维变换空间中每个特征的“自相似性”。
-
矩阵的非对角线部分表示不同特征之间的相关性。
现在,我们通过可视化之前构建的线性模型和 ReLU 模型在两个不同稀疏性水平(0.1 和 0.9)下的 W^T@W 值来进行比较。你可以看到,当稀疏性值高达 0.9 时,非对角元素相比稀疏性为 0.1 时变得更大(实际上你在两个模型的输出之间看不到太多差异)。这一观察结果表明,当稀疏性较高时,不同特征之间的相关性更容易被学习到。
作者提供的图像:稀疏性为 0.1 的矩阵
作者提供的图像:稀疏性为 0.9 的矩阵
结论
在这篇博客文章中,我做了一个简单的实验,通过比较具有较少维度而特征较多的线性模型和 ReLU 模型,来介绍神经网络中叠加的形成。我们观察到,ReLU 激活引入的非线性,加上适度的稀疏性,可以帮助模型形成叠加。
在现实应用中,这些应用比我的简单示例要复杂得多,叠加是表示神经模型中复杂关系的重要机制,特别是在视觉模型或大型语言模型(LLM)中。
参考文献
1 放大:电路介绍。 distill.pub/2020/circuits/zoom-in/
2 具有叠加的玩具模型。 transformer-circuits.pub/2022/toy_model/index.html
使用大语言模型的监督微调(SFT)
了解 SFT 如何从理念到实际实现……
·发表于Towards Data Science ·15 分钟阅读·2024 年 1 月 16 日
--
(照片由Chris Ried提供,来源于Unsplash)
大语言模型(LLMs)通常经过几个阶段的训练,包括预训练和多个微调阶段;见下文。尽管预训练成本高(即需要数十万美元的计算资源),与之相比,微调 LLM(或进行上下文学习)的成本要低得多(即几百美元,甚至更低)。鉴于高质量的预训练 LLM(如 MPT、Falcon 或 LLAMA-2)已经广泛可用并且免费使用(即使是商业用途),我们可以通过在相关任务上微调 LLM 来构建各种强大的应用。
训练 LLM 的不同阶段(由作者创建)
最近 AI 研究中最广泛使用的微调方法之一是监督微调(SFT)。这种方法通过整理一组高质量的 LLM 输出数据集,使用标准的语言建模目标对模型进行直接微调。SFT 简单且廉价,并且是对齐语言模型的有用工具,这使得它在开源 LLM 研究社区及更广泛的领域中变得非常流行。在本概述中,我们将概述 SFT 背后的理念,并进一步探讨相关的……
使用 Python 进行供应链过程调度
使用线性规划来提高奢侈产品仓库中增值服务的生产能力。
·发布于《数据科学前沿》 ·阅读时间 8 分钟·2024 年 5 月 24 日
--
使用 Python 优化仓库过程调度 — (作者图片)
根据我的经验,奢侈品牌在分销中心面临的主要挑战与入库物流有关。
收到后,物品必须经过多次增值服务才能完成入库流程并返回库存。
增值服务要求示例 — (作者图片)
例如,从法国进口到中国的一只包包需要进行特定的中文标签标注、一系列质量检查以及防盗标签。
这些过程可能创造瓶颈,从而延迟分销并导致门店库存短缺。
作为数据科学家,你如何利用线性规划来减少瓶颈并最大化生产力?
在本文中,我们将使用作业车间问题来优化这些过程的调度,并最大化整体的入库生产力。
我将使用 Google OR 工具提供一种最佳过程调度解决方案,以将入库能力提升多达 48%。
Summary
I. Value-Added Services for Luxury Products
II. Problem Statement of Inbound Process Scheduling
1\. Inbound Operations Optimization for Luxury Products
2\. Problem Statement: The Job-Shop Problem
III. Conclusion
奢侈产品的增值服务
我所进行的大多数重组项目是为了零售、快速消费品(FMCG)或汽车行业,通过最小化劳动力和设备的使用来降低成本。
降低成本的重组项目示例 [查看更多: 链接] — (作者图片)
对于奢侈品牌,优先考虑的是不同的因素,考虑到商品的价值和需求的变动性。
门店团队:“SS2024 系列需要在 6 月的第一周之前送达门店。”
物流团队面临压力,必须确保产品及时接收、准备并发货,以满足门店的需求。
为了说明这一点,我将以一个实际的例子来讲解:一家法国奢侈品牌在上海运营配送中心,向中国的 35 家门店进行配送。
奢侈品牌的配送中心 — (图片来源:作者)
这个配送中心接收从法国进口到本地市场的商品(服装、包包和配饰)。
仓库中的产品流动 — (图片来源:作者)
在接收和增值服务完成后,商品可以经过两种不同的流动路径:
-
交叉对接流动:商品在当天发货到门店
⌛ KPI:接收和发货之间的周转时间
-
库存流动:商品被存放到库存中(几天或几周)后,再被订购、拣选并发货到门店
⌛ KPI:接收和存放之间的周转时间
这两个指标有一个共同的问题,就是依赖于仓库的增值服务(VAS)能力。
这些增值服务是什么?
- 操作 1 — 防盗标签:操作员为商品贴上自报警标签,防止在门店被盗
防盗标签 - (图片来源:作者)
- 操作 2 — 贴标签:操作员打印本地语言标签并进行标签缝制
标签示例 — (图片来源:作者)
- 操作 3 — 配件与重新包装:操作员将商品放入销售包装,并添加赠品(GWP)、个人说明和正品证书
赠品包装和证书示例 — (CAD 模型来源:作者)
完成这三个步骤后,商品可以存放在库存区或发货到门店。
可能会出什么问题?
如果整体流程的处理能力(件/天)过低,当面临入库量高峰时,这可能迅速成为瓶颈。
问题陈述:仓库增值服务调度
奢侈品入库操作优化
你是这家标志性奢侈品牌的物流部门数据科学经理,专注于时尚、香水和手表领域。
该配送中心的入库经理请求你支持,减少由于增值服务调度低效导致的瓶颈。
她的团队每天接收数千件成衣(成品服装),包括:
-
需要贴标签和重新包装的1 件女装
-
需要贴标签、防盗标签和重新包装的手袋
-
需要防盗标签、贴标签和重新包装的1 条皮带
由于这些物品是一起销售的,它们需要在完成以下步骤后同时准备好:
- 接收团队将托盘从卡车上卸下,并将其放入暂存区。
卸货并将托盘转移到暂存区 — (CAD 模型由作者提供)
- 机器 1 — 防盗标签: 操作员为每个手袋和皮带放置防盗标签。
2 个工作站,操作员在每个手袋和皮带上放置防盗标签 — (CAD 模型由作者提供)
- 机器 2—标签: 在专用区域打印标签后,标签会被缝制到皮带、手袋和裙子上。
4 个工作站,操作员执行标签缝制工作 — (CAD 模型由作者提供)
- 机器 3 — 配套和重新包装: 操作员为每个物品添加认证证书并进行精细包装。
4 个工作站,操作员执行重新包装工作 — (CAD 模型由作者提供)
完成这些步骤后,货物被转移到最终暂存区等待发货(流向 1),或被放入库存区(流向 2)。
目标: 达到每小时组装的最大生产率(套数/小时)。
问题描述:作业车间问题
作业车间调度问题(JSSP) 是一个 NP 难问题,由一组作业定义,机器必须按照每个作业的特定顺序执行。
在我们的例子中,每个项目都有一个作业,并且它们必须(可以)同时执行。
一个包含 3 个作业和 3 台机器的例子 — (图片由作者提供)
上表定义了每个作业的执行时间(分钟)和机器处理顺序。
例如,作业 2(手袋) 从使用机器 1 放置防盗标签开始(6 分钟),然后是使用机器 2 进行标签缝制(4 分钟),最后以使用机器 3 进行配套和包装结束(3 分钟)。
我们在如何使用机器方面有一些约束:
-
这些机器一次只能执行一个作业。
-
一旦开始,机器无法在指定作业完成之前中断。
目标是 最小化完工时间,即完成所有作业的总时间。
基准是什么?
朴素解法:一次执行一个作业周期
第一个朴素方法 — (图片由作者提供)
我们假设 VAS 团队负责人将作业按顺序安排,并避免并行执行任何作业。
结果
-
完工时间:30 分钟
-
生产率:2 套/小时
评论
这种简单的方法在生产力方面最差。因为作业是按顺序处理的,机器常常处于空闲状态(未使用)。
问题: 如果我们并行执行作业,结果会怎样?
最优解
这个例子适用于使用 Google OR-Tools 解决的作业车间调度问题。
OR-Tools 是 Google 提供的一个开源工具集合 ,用于组合优化。
目标是从众多可能的解决方案中找到最佳方案。
我已经使用它进行了多个案例研究。
- Samir Saci,使用 Google AI 设计路径规划算法以提高仓库生产率
基于旅行商问题设计的路径规划算法,结合 Google AI 线性优化进行实现……
[towardsdatascience.com
- Samir Saci,使用 Python 的线性规划优化劳动力规划
你需要雇佣多少临时工来消化你的每周工作量,同时确保……
towardsdatascience.com
让我们使用这个库来找到优化的排序,以减少这个特定流程集的完工时间。
使用线性规划优化过程调度
结果:优化方案 vs. 朴素方案
第一个朴素方法 — (图像由作者提供)
使用 Google OR-Tools 的优化方案 — (图像由作者提供)
上面的两张图分别代表了初始方案(朴素方案:一次处理 1 个工作)和优化方案(并行任务处理)。
结果
-
总完工时间: 16 分钟 (-47%)
-
生产率: 3.75 套/小时 (+85%)
-
每周期空闲时间: 18 分钟 (-71.4%)
结果令人满意。
如何获得这些结果?
使用 Python 构建优化模型
初始化你的线性规划模型
在列表 jobs_data 中,你定义了每个工作的操作,包括与其相关的机器和时间安排。
初始化变量并创建序列
添加约束并设置求解器
求解器优化方案
输出
Optimal Schedule Length: 16 min
Machine 1: job_2_1 job_3_2
[0,6] [6,10]
Machine 2: job_3_1 job_1_1 job_2_2
[0,3] [3,7] [7,11]
Machine 3: job_1_2 job_3_3 job_2_3
[7,10] [10,13] [13,16]
根据这个输出,我们可以绘制更新后的时间表:
使用 Google OR-Tools 的优化方案 — (图像由作者提供)
考虑到约束条件,这个解决方案最大限度地减少了机器的空闲时间,并提供了最高的生产力(台数/小时)。
结论
通过实施一个智能调度解决方案,我们将生产力提高了+48%,最大化了资源利用率。
该解决方案基于一个简单的场景,使用了一个单一的装配线(每种类型 1 台机器)。
我们能通过改变条件来提高生产力吗?
使用空闲序列(黄色)优化的解决方案——(图片来源:作者)
在上面的图表中,我已经标出了我们可以在空闲时间添加的潜在额外工作:
-
机器 1:1 个序列,持续 4 分钟,相当于工作 3 的时间
-
机器 2:1 个序列,持续 4 分钟,相当于工作 1 和工作 2 的时间
-
机器 3:2 个序列,持续 4 分钟,相当于工作 1、2 和 3 的时间
问题:
- 如果我们在Cycle n的空闲序列期间开始Cycle n+1的工作,平均生产力会是多少?
超越
如果我们为标签缝纫设置平行工作站,整体生产力会受到什么影响?
排队理论的过程定义——(图片来源:作者)
这个问题可以通过排队理论来解答。
使用排队理论设计打包站——(图片来源:Samir Saci)
在这篇文章中了解更多,
使用 Python 应用排队理论的多个原则来设计电商包裹打包过程……
towardsdatascience.com
关于我
让我们在Linkedin和Twitter上连接,我是一名供应链工程师,利用数据分析来改善物流操作并降低成本。
如果您需要关于供应链转型的咨询或建议,请通过Logigreen Consulting与我联系。
如果您对数据分析和供应链感兴趣,可以查看我的网站。
一篇关注数据科学、个人生产力、自动化、运筹学和可持续发展的技术博客……
samirsaci.com](https://samirsaci.com/?source=post_page-----6811cd9e650a--------------------------------)
💌 免费直接发送到您的邮箱的新文章:Newsletter
📘 您的供应链分析完整指南:Analytics Cheat Sheet
参考文献
- Google AI, Google OR-Tools 库, 链接
支持向量分类器,解释:带有迷你 2D 数据集的视觉指南
分类算法
找到最佳的“分隔线”来区分不同的类别?嗯,当然……
·发表于 Towards Data Science ·14 分钟阅读·2024 年 10 月 1 日
--
⛳️ 更多 [分类算法](https://medium.com/@samybaladram/list/classification-algorithms-b3586f0a772c),解释如下: · 虚拟分类器 · K 最近邻分类器 · 伯努利朴素贝叶斯 · 高斯朴素贝叶斯 · 决策树分类器 · 逻辑回归 ▶ 支持向量分类器 · 多层感知器
“用于分类的支持向量机(SVM)遵循一个非常基础的原则——它试图找到一条最优的分隔线,将两个类别分开。”但如果我再听到这种过于简化的解释一次,我可能真会把头埋进枕头里尖叫。
虽然前提看起来很简单,但 SVM 是那种包含复杂数学运算的算法,我花费了大量时间才搞懂。为什么它叫做“机器”?我们为什么需要支持向量?为什么有些点突然变得不重要?为什么它必须是直线——哦,等等,是直超平面??? 然后还有优化公式,这个公式 apparently 难得需要一个叫做对偶形式的版本来处理。等等,现在我们还需要另一个算法叫做 SMO 来解决这个问题?那些 scikit-learn 自动输出的对偶系数又是怎么回事?如果这还不够,当一条直线不够用时,我们又开始使用神奇的“核技巧”?我们为什么需要这些技巧?而且为什么没有教程展示实际的数字?!
在本文中,我试图停止这种支持向量机的疯狂。在花了几个小时尝试真正理解这个算法后,我将尝试用实际的数字(当然,还有其可视化)来解释实际发生了什么,而不涉及复杂的数学,适合初学者。
所有可视化:作者使用 Canva Pro 创建。已优化为适合移动端;在桌面端可能会显得过大。
定义
支持向量机(SVM)是监督学习模型,主要用于分类任务,尽管它们也可以适应回归任务。SVM 旨在找到能够最好地将数据集分成不同类别的那条线(唉…),并最大化这些类别之间的间隔。
尽管 SVM 很复杂,但它仍然可以被视为机器学习中的基础算法之一。
“支持向量”是那些距离决策边界最近的点,它们实际上可以定义这条线。那么,那个“机器”又是什么?虽然其他机器学习算法也可以包括“机器”,但 SVM 的命名可能部分来源于它被开发时的历史背景。就这样。
📊 使用的数据集
为了理解 SVM 的工作原理,最好从一个样本较少、维度较小的数据集开始。我们将以这个简单的二维小数据集(灵感来自1)作为示例。
列:温度(0–3)、湿度(0–3)、打高尔夫(是/否)。训练数据集有 2 个维度和 8 个样本。
我们不会直接解释训练过程的步骤,而是将从关键字到关键字,看看 SVM 实际上是如何工作的:
第一部分:基本组件
决策边界
SVM 中的决策边界是算法确定的最佳分隔不同类别数据的线(或在高维中称为“超平面”)。
这条线会尽力把大多数“是”类点放在一边,大多数“否”类点放在另一边。然而,对于那些不能线性分割的数据,这条边界不会是完美的——一些点可能会出现在“错误”的一侧。
一旦确定了这条线,任何新的数据都可以根据它位于决策边界的哪一侧来进行分类。
在我们的高尔夫例子中,决策边界将试图将“YES”(打高尔夫)和“NO”两类数据点分开。SVM 会尝试定位这条线,尽管用一条直线无法做到完美分隔。此时,凭借我们的眼光,这条线看起来是一个不错的分隔线。
线性可分性
线性可分性指的是我们是否可以画出一条直线,完美地将两个类别的数据点分开。如果数据是线性可分的,SVM 可以找到一个清晰的、硬的边界来区分不同类别。然而,当数据不是线性可分的(如我们的情况)时,SVM 需要使用更先进的技术。
在训练集里,不论我们如何画线,都无法将两类数据完全分开。如果我们忽略索引 1 和 8,现在就能分开它们了。
间隔
在 SVM 中,间隔是指决策边界到每个类别的最近数据点之间的距离。这些最近的点称为支持向量。
SVM 的目标是最大化这个间隔。较大的间隔通常能带来更好的泛化能力——即能够正确分类新的、未见过的数据点。
然而,由于数据通常并不是完全可分的,SVM 可能会采用软间隔的方法。这允许一些点位于间隔内,甚至处于决策边界的错误一侧,从而在完美分隔与构建更强健的分类器之间做出权衡。
SVM 会尝试将决策边界定位,创造出尽可能宽的间隔,同时尽量将大部分“YES”和“NO”实例分开。
硬间隔与软间隔
硬间隔 SVM 是理想情况,在这种情况下,所有数据点都能被决策边界完美地分开,不会出现任何误分类。在这种情况下,间隔是“硬”的,因为它不允许任何数据点处于决策边界的错误一侧或间隔内。
另一方面,软间隔 SVM 允许一定的灵活性。它允许一些数据点被误分类,或位于间隔内。这使得 SVM 能够在以下两者之间找到一个良好的平衡:
-
最大化间隔
-
最小化分类错误
在我们的案例中,硬间隔方法不可行,因为数据不是线性可分的。因此,对于我们的数据集,必须采用软间隔方法。在软间隔 SVM 中,可能允许像 ID 1 和 ID 8 这样的点处于决策边界的“错误”一侧,如果这能带来更好的总体分类器。
距离计算
在 SVM 中,距离计算在训练和分类中都起着重要作用。点x到决策边界的距离由以下公式给出:
|w · x + b| / ||w||
其中,w是垂直于超平面的权重向量,b是偏置项,||w||是w的欧几里得范数。
通过这种方式,我们可以在不画出超平面的情况下,看到哪些点离超平面最近。
支持向量
支持向量是距离超平面最近的数据点。它们之所以重要,是因为:它们“支持”超平面,定义了其位置。
支持向量之所以特别,是因为它们是确定决策边界的唯一关键点。其他所有点可以移除而不改变边界的位置。这是支持向量机(SVM)的一个关键特性——它依据最关键的点来做出决策,而不是所有的数据点。
对于这个超平面,我们有 3 个支持向量位于边缘上。在某些情况下,2 个被错误分类的数据点也可以视为支持向量。
松弛变量
在软间隔 SVM 中引入了松弛变量,用以量化每个数据点的误分类或边缘违背程度。它们被称为“松弛”变量,因为它们为模型提供了一些松弛或灵活性来拟合数据。
在 SVM 中,松弛变量 ξᵢ 可以通过以下方式计算:
ξᵢ = max(0, 1 — yᵢ(w · xᵢ + b))
其中
· w 是权重向量
· b 是偏置项
· xᵢ 是输入向量
· yᵢ 是相应的标签
这个公式仅在类标签 yᵢ 为 {-1, +1} 格式时有效。它优雅地处理了两类问题:
· 正确分类且位于边缘之外的点:ξᵢ = 0
· 被错误分类或违反边缘的点:ξᵢ > 0
使用 {-1, +1} 标签保持了 SVM 的数学对称性并简化了优化,与 {0, 1} 标签不同,后者需要为每一类创建单独的情况。
在我们的高尔夫数据集中,点 (3,3) — NO 最终位于我们的边界的“YES”一侧。我们会为该点分配一个松弛变量,以衡量它偏离错误一侧的距离。同样,如果 (2,0) — NO 被正确分类但位于边缘内,它也会得到一个松弛变量。
在我们的高尔夫数据集中,点 (3,3) — NO 最终位于我们的边界的“YES”一侧。我们会为该点分配一个松弛变量,以衡量它偏离错误一侧的距离。同样,如果 (2,0) — NO 被正确分类但位于边缘内,它也会得到一个松弛变量。
硬间隔的原始形式
原始形式是支持向量机(SVM)优化问题的原始表述。它直接表达了在特征空间中寻找最大间隔超平面的目标。
简单来说,原始形式的目标是:
-
找到一个能正确分类所有数据点的超平面。
-
最大化该超平面与来自每个类别的最近数据点之间的距离。
原始形式是:
最小化: (1/2) ||w||²
受限条件: yᵢ(w · xᵢ + b) ≥ 1 对所有 i 都成立
其中
· w 是权重向量
· b 是偏置项
· xᵢ 是输入向量
· yᵢ 是相应的标签(+1 或 -1)
· ||w||² 是 w 的平方欧几里得范数
在省略索引 1 和 8 的情况下,我们正在尝试找到最佳的边界,以便获得更大的边距。
如果我们选择具有较小边距的超平面,它会使目标函数的值更高,而这不是我们想要的。
软边距的原始形式
记住,软边距 SVM 是原始(硬边距)SVM 的扩展,它允许一些误分类?这一变化在原始形式中有所体现。软边距 SVM 的原始形式变为:
最小化: (1/2) ||w||² + C Σᵢ ξᵢ
约束条件:yᵢ(w · xᵢ + b) ≥ 1 — ξᵢ 对所有 i,ξᵢ ≥ 0 对所有 i
其中
· C 是惩罚参数
· ξᵢ 是松弛变量
· 所有其他变量与硬边距情况下相同
误分类数据点的惩罚作为额外值贡献到目标函数中,以便最小化。
假设我们选择了另一个稍微靠近索引 8 的超平面。目标值现在变得更高。从误分类点的距离越平衡,总的惩罚就越小。
对偶形式
这里有个坏消息:原始形式可能求解较慢且难以解决,尤其是在处理复杂数据时。
对偶形式提供了解决 SVM 优化问题的替代方法,通常能够带来计算上的优势。其形式如下:
最大化:Σᵢ,ⱼ(αᵢyᵢ) - ½ΣᵢΣⱼ(αᵢαⱼyᵢyⱼ(xᵢ · xⱼ)) 约束条件:0 ≤ αᵢ ≤ C 对所有 i,Σᵢαᵢyᵢ = 0
其中:
· αᵢ 是拉格朗日乘子(对偶变量)
· yᵢ 是类标签(+1 或 -1)
· xᵢ 是输入向量
· C 是正则化参数(αᵢ 的上界)
· (xᵢ · xⱼ) 表示 xᵢ 和 xⱼ 之间的点积
除了训练数据本身,唯一出现在对偶形式中的其他成分是拉格朗日乘子(αᵢ)。
拉格朗日乘子
正如我们在对偶形式中看到的,当我们将原始问题转换为对偶形式时,拉格朗日乘子(αᵢ)就会出现(这也是它们被称为对偶系数的原因)。如果你注意到了,权重和偏置不再存在!
每个训练集中的数据点都有一个关联的拉格朗日乘子。好处是,拉格朗日乘子使得理解问题变得更加容易:
-
解释:
-
αᵢ = 0:该点被正确分类并且位于边距外。这个点不会影响决策边界。
-
0 < αᵢ < C:该点位于边距边界上。这些点被称为“自由”或“无界”支持向量。
-
αᵢ = C:该点要么位于边界上,要么位于边界内部(包括误分类点)。这些点被称为“边界”支持向量。
-
-
与决策边界的关系:
w = Σᵢ(αᵢ yᵢ xᵢ),
b = yᵢ — Σⱼ(αᵢ yⱼ(xⱼ · xᵢ))
其中,yᵢ是任何(无界)支持向量的标签。
这意味着最终的决策边界仅由具有非零αᵢ的点决定!
结果表明,算法决定我们原来的超平面是最优的,只是需要通过将所有权重减半来增大间隔。这使得所有点都变成了支持向量,但由于数据集本身较小,这没有问题。😅
顺序最小优化(Sequential Minimal Optimization)
记住,我们还没有真正展示如何获得最优的拉格朗日乘子(αᵢ)?解决这个问题的算法称为顺序最小优化(SMO)。下面是我们如何获得这些值的简化视图:
-
从所有αᵢ为零开始。
-
重复选择并调整两个αᵢ以改进解。
-
使用简单的数学快速更新这些对。
-
确保所有更新遵循支持向量机(SVM)约束。
-
重复直到所有αᵢ都“足够好”为止。
-
具有αᵢ > 0 的点成为支持向量。
这种方法高效地解决了 SVM 优化问题,无需繁重的计算,使其在大数据集上具有实用性。
决策函数
通过使用对偶形式求解 SVM 优化问题并获得拉格朗日乘子后,我们可以定义决策函数。该函数决定了训练后的 SVM 模型如何对新的、未见过的数据点进行分类。
f(x) = Σᵢ(αᵢyᵢ(xᵢ · x)) + b
这里,αᵢ是拉格朗日乘子,yᵢ是类别标签(+1 或-1),xᵢ是支持向量,x是待分类的输入向量。新点 x 的最终分类由f(x)的符号决定(即“+”或“-”)。
注意,这个决策函数仅使用支持向量(具有非零αᵢ的数据点)来分类新的输入数据,这就是 SVM 算法的核心原理!
🌟 支持向量分类器代码
上述结果可以通过以下代码获得:
import numpy as np
import pandas as pd
from sklearn.svm import SVC
# Create DataFrame
df = pd.DataFrame({
'🌞': [0, 1, 1, 2, 3, 3, 2, 3, 0, 0, 1, 2, 3],
'💧': [0, 0, 1, 0, 1, 2, 3, 3, 1, 2, 3, 2, 1],
'y': [1, -1, -1, -1, 1, 1, 1, -1, -1, -1, 1, 1, 1]
}, index=range(1, 14))
# Split into train and test
train_df, test_df = df.iloc[:8].copy(), df.iloc[8:].copy()
X_train, y_train = train_df[['🌞', '💧']], train_df['y']
X_test, y_test = test_df[['🌞', '💧']], test_df['y']
# Create and fit SVC model
svc = SVC(kernel='linear', C=2)
svc.fit(X_train, y_train)
# Add Lagrange multipliers and support vector status
train_df['α'] = 0.0
train_df.loc[svc.support_ + 1, 'α'] = np.abs(svc.dual_coef_[0])
train_df['Is SV'] = train_df.index.isin(svc.support_ + 1)
print("Training Data, Lagrange Multipliers, and Support Vectors:")
print(train_df)
# Print model parameters
w, b = svc.coef_[0], svc.intercept_[0]
print(f"\nModel Parameters:")
print(f"Weights (w): [{w[0]}, {w[1]}]")
print(f"Bias (b): {b}")
print(f"Decision function: f(🌞,💧) = ({w[0]})🌞 + ({w[1]})💧 + ({b})")
# Make predictions
test_df['ŷ'] = svc.predict(X_test)
print("\nTest Data and Predictions:")
print(test_df)
第二部分:核技巧
如我们所见,无论如何设置超平面,我们始终无法完美地将两类数据分开。实际上,我们可以做一些“技巧”,使得数据可以被分开……尽管它不再是线性可分的。
输入空间与特征空间
输入空间指的是数据特征的原始空间。在我们的高尔夫数据集中,输入空间是二维的,由温度和湿度组成。此空间中的每个数据点代表某个具体天气条件下,是否有人决定打高尔夫。
特征空间则是输入空间的一个转换版本,SVM 实际上在特征空间中执行分类。有时,在线性不可分的输入空间中,数据映射到高维特征空间后变得可分。
如我们到目前为止所尝试的,无论选择什么超平面,我们都无法将这两个类别线性分开。除了使用🌞和💧,特征空间可能还包括类似🌞²、💧²、🌞×💧的组合。这将把我们的二维输入空间转变为五维特征空间。如果你注意到的话,我们现在可以找到一个超平面,能够完美地分开这两个类别!
核及隐式变换
核是一个计算两个数据点之间相似性的函数,隐式地将它们表示在一个更高维的空间(特征空间)中。
假设有一个函数 φ(x),它将每个输入点 x 转换到一个更高维的空间。例如:φ : ℝ² → ℝ³, φ(x,y) = (x, y, x² + y²)
常见核及其隐式变换:
a. 线性核:K(x,y) = x · y
- 变换:
φ(x) = x (恒等变换)
- 这实际上并不会改变空间,但对于线性可分的数据来说是有用的。
b. 多项式核:K(x,y) = (x · y + c)ᵈ
- 变换(对于 d = 2,c = 1 在 ℝ² 中):
φ(x₁,x₂) = (1, √2x₁, √2x₂, x₁², √2x₁x₂, x₂²)
- 这涵盖了所有最高为 d 次的多项式项。
c. RBF 核:K(x,y) = exp(-γ||x - y||²)
- 变换(作为一个无穷级数):
φ(x₁,x₂) = exp(-γ||x||²) * (1, √(2γ)x₁, √(2γ)x₂, …, √(2γ²/2!)x₁², √(2γ²/2!)x₁x₂, √(2γ²/2!)x₂², …, √(2γⁿ/n!)x₁ⁿ, …)
- 可以被看作是一个相似性度量,随着距离的增加而减小。
这旨在说明核如何转换输入空间。实际上,特征空间中每个点的计算本身并不会执行,因为计算非常昂贵,这就是为什么它被称为隐式的原因。
核技巧
核技巧的“技巧”部分在于,我们可以仅使用核函数,在这个更高维的空间中执行操作,而无需显式计算变换 φ(x)。
注意,在对偶形式中,数据点仅以点积的形式出现 (xᵢ · xⱼ)。这就是核技巧的作用所在。我们可以将这个点积替换为核函数:(xᵢ · xⱼ) → K(xᵢ, xⱼ)
如果我们仅使用原始形式,则无法进行此过程,这也是为什么对偶形式更可取的主要原因之一!
这个替代方法隐式地将数据映射到更高维空间而不显式计算变换。
使用核技巧的决策函数
对于新点 x 的决策函数结果为:
f (x) = sign(Σᵢ αᵢyᵢK(xᵢ, x) + b)
其中求和是针对所有支持向量(点的 αᵢ > 0)。
🌟 支持向量分类器(带核技巧)代码总结
上述结果可以通过以下代码得到:
import numpy as np
import pandas as pd
from sklearn.svm import SVC
# Create DataFrame
df = pd.DataFrame({
'🌞': [0, 1, 1, 2, 3, 3, 2, 3, 0, 0, 1, 2, 3],
'💧': [0, 0, 1, 0, 1, 2, 3, 3, 1, 2, 3, 2, 1],
'y': [1, -1, -1, -1, 1, 1, 1, -1, -1, -1, 1, 1, 1]
}, index=range(1, 14))
# Split into train and test
train_df, test_df = df.iloc[:8].copy(), df.iloc[8:].copy()
X_train, y_train = train_df[['🌞', '💧']], train_df['y']
X_test, y_test = test_df[['🌞', '💧']], test_df['y']
# Create and fit SVC model with polynomial kernel
svc = SVC(kernel='poly', degree=2, coef0=1, C=1)
svc.fit(X_train, y_train)
# Add Lagrange multipliers and support vector status
train_df['α'] = 0.0
train_df.loc[svc.support_ + 1, 'α'] = np.abs(svc.dual_coef_[0])
train_df['Is SV'] = train_df.index.isin(svc.support_ + 1)
print("Training Data, Lagrange Multipliers, and Support Vectors:")
print(train_df)
# Make predictions
test_df['ŷ'] = svc.predict(X_test)
print("\nTest Data and Predictions:")
print(test_df)
注意:由于 SVC 存在一些数值不稳定性,我们无法使得 scikit-learn 中的截距与手动计算一致……这就是为什么我没有展示如何手动计算偏置(尽管它应该与线性核的计算方法相同)。
关键参数
在 SVM 中,关键参数是惩罚/正则化参数 C:
-
大 C:努力准确分类所有训练点,可能会导致过拟合
-
小 C:允许更多的误分类,但目标是得到一个更简单、更通用的模型
当然,如果你使用的是非线性核,你还需要调整与该核相关的度数(和系数)。
最后的备注
我们已经讨论了很多关于 SVM 的关键概念(以及它们的工作原理),但主要思想是这样的:这全是关于找到合适的平衡。你希望你的 SVM 学会识别数据中的重要模式,而不是过于努力地让每一个训练数据都落在超平面的正确一侧。如果它太严格,可能会错过全局;如果它太灵活,可能会看到一些并不存在的模式。诀窍是调整你的 SVM,使其能够识别出真正的趋势,同时仍具有足够的适应性来处理新数据。把这个平衡做好,你就有了一个可以解决各种分类问题的强大工具。
进一步阅读
对于关于支持向量机及其在 scikit-learn 中实现的详细解释,读者可以参考官方文档,该文档提供了关于其使用和参数的全面信息。
技术环境
本文使用的是 Python 3.7 和 scikit-learn 1.5。虽然所讨论的概念通常适用,但不同版本的具体代码实现可能略有不同。
关于插图
除非另有说明,所有图片均由作者创作,融合了来自 Canva Pro 的授权设计元素。
参考文献
1 T. M. Mitchell, 机器学习 (1997), McGraw-Hill Science/Engineering/Math,第 59 页
𝙎𝙚𝙚 𝙢𝙤𝙧𝙚 𝘾𝙡𝙖𝙨𝙨𝙞𝙛𝙞𝙘𝙖𝙩𝙞𝙤𝙣 𝘼𝙡𝙜𝙤𝙧𝙞𝙩𝙝𝙢𝙨 𝙝𝙚𝙧𝙚:
分类算法
查看列表8 个故事
你可能还喜欢:
回归算法
查看列表5 个故事!一只戴着辫子和粉色帽子的卡通娃娃。这个“虚拟”娃娃,带着基础设计和心形装饰的衬衫,形象地代表了机器中的虚拟回归器概念。就像这个玩具般的人物是一个简化的、静态的人物表示一样,虚拟回归器也是一种基本模型,为更复杂的分析提供基准。
集成学习
查看列表4 个故事!
简化支持向量机——二元分类简明介绍
MLBasics #4:二元分类之王——支持向量机之旅
·发表于Towards Data Science ·阅读时间 7 分钟·2024 年 6 月 21 日
--
图片来自作者。
在数据和计算机程序的世界里,机器学习的概念可能听起来像一个难解的难题,充满了复杂的数学和深奥的思想。
这就是为什么今天我想放慢速度,看看那些让这一切工作的小知识点,通过我MLBasics 系列的新一期。
今天的议程是了解支持向量机。
这个强大的工具帮助我们将数据分类到不同的类别,但…
它是如何工作的?
让我们尝试简化支持向量机模型👇🏻
什么是支持向量机(SVM)?
支持向量机(SVM)是一种监督式机器学习算法,旨在寻找一个超平面,将数据点最佳地分成两类。
挑战在于,存在无限多的超平面可以做到这一点。因此,SVM 的目标是识别能够以最大间隔将类别最佳分开的超平面。
LLM 的软硬件共同优化策略——第二部分(软件)
软件正在吞噬世界。LLM 的软硬件生态是什么样的?有哪些新兴的库/软件框架可以提升 LLM 的性能?
·发布于 Towards Data Science ·7 分钟阅读·2024 年 1 月 2 日
--
随着新的 LLM 模型和功能不断涌现(查看 hugging face LLM 排行榜),软件工具和库的发布速度也在加快。这一快速进展也激发了 AI 硬件方面的众多创新。在从系统角度优化 LLM 时,至关重要的是要理解,尽管 Meta、Google、OpenAI、Nvidia、斯坦福大学等大公司和研究机构每天都有新的研究成果涌现,但软件堆栈/库并不能立即将所有内容直接转化为硬件执行。只有少数选定的软件功能可以得到支持,这些功能需要几个月(大约 6 个月)的开发才能投入生产。如果这些功能需要在 AI 硬件加速器中得到支持,开发周期会更长(2-4 年),尤其在发生架构变化时。解决软件与硬件优化之间的这种差距是一个重大挑战,而我们正致力于在这一系列文章中 tackling 这一问题!
图片由作者提供
新兴的软件工具和库不仅服务于 LLM 的训练,也支持推理。在这篇文章中,我们将……
从 Sphinx 切换到 MkDocs 文档 —— 我得到了什么,失去了什么?
关于执行此切换的指南,以及它们之间的比较
·发布于Towards Data Science ·11 分钟阅读·2024 年 2 月 2 日
--
图片由melanfolia提供,来源于Unsplash
“一个项目的好坏取决于它的文档” —— 这是我之前的三部分文章系列中的一句名言,强调了文档的重要性。从那时起,我了解到文档有不同的种类,其中较为常见的包括:
-
技术文档:描述项目内部运作的技术方面,可以是过程文档或产品文档
-
业务文档:描述它解决了哪些业务问题或实现了哪些业务目标
存在一种类型的文档介于两者之间,它们是手册、指南和教程。特别是当你希望技术用户与项目合作或使用项目,或希望业务用户理解项目并获得支持时,这一点尤为重要。
随着文档内容的增加,文档变得越来越复杂,如何以用户友好且易读的格式展示文档可能会变得很棘手。例如,教程不应该嵌入到技术文档中,且……
句法:语言的形式
人类和计算机中的语言处理:第二部分
你怎么知道这是一个句子?
·发表在Towards Data Science ·22 分钟阅读·2024 年 3 月 21 日
--
第一部分是:
聊天机器人是谁(对你来说是什么)? 结语:房间里的四只大象与聊天机器人
这是第二部分:
1. 句法深奥,语义任意
2. 语法
-
2.1. 成分(短语结构)语法
-
2.2. 依赖语法
3. 句法作为类型化
-
3.1. 句法类型检查
-
3.2. 解析和类型化
-
3.3. 前群语法
4. 超越句子
-
4.1. 我们为什么造句子?
-
4.2. 语言表达和网络层
5. 超越句法
-
5.1. 语义上下文敏感性
-
5.2. 句法上下文敏感性
-
5.3. 交流是共享语义上下文的过程
第三部分:
第四部分:
语言作为通用学习机器
1. 句法深奥,语义任意
人们讲多种语言。说不同语言的人通常互相不理解。如何可能有一个语言的普适理论呢?
生命在许多物种中也是多样化的,不同物种通常不能互相交配¹。但生命是一种自我复制的普遍能力,而生物学是生命的一个普遍理论。
一般语言学基于诺姆·乔姆斯基的笛卡尔假设²:所有语言都源自于我们物种固有的普遍语言能力。这一观点认为,我们所有的不同语言共享大脑中嵌入的相同深层结构。由于不同语言为相同事物赋予不同的词汇,因此词汇与意义的语义分配并不属于这些普遍深层结构的一部分。乔姆斯基的一般语言学主要关注的是一般句法。它还研究(或者曾经研究)将深层句法结构转化为在特定语言中可观察到的表层结构的过程,正如生物学研究遗传机制如何导致特定有机体一样。稍微简化一下,乔姆斯基的理论意味着
-
句法是现代语言学的主要研究对象,而
-
语义学以互补的方式在以下领域进行研究:
— 意义哲学,无论是以符号学的名义,还是在结构主义的众多化身中;以及在不同方法中
— 搜索引擎工程、信息检索索引和目录、用户画像,以及定向广告。
然而,语言学中研究深层结构到表层结构的路径与生物学中研究的不同之处在于:
-
在生物学中,生命深层结构的载体是直接可观察的,并且在遗传学中有实证研究,而
-
在语言学中,句法的深层结构并不可直接观察,而只是被假设存在,就像乔姆斯基的笛卡尔基础一样,寻找实际载体的任务则留给未来的科学。
这使得关于普遍句法结构的笛卡尔假设站不住脚。大型语言模型的出现可能会引发这一理论基础的重大变革。我们与聊天机器人的大多数早期互动似乎暗示,句法与语义之间的分界线可能并不像传统假设的那样明确。
要理解范式的转变,我们需要先理解该范式。要有机会理解大型语言模型,我们需要对语言学中早期发展出的语言模型有一个基本了解。在这节课和下一节课中,我们将分别穿越句法和语义的理论。
2. 语法
2.1. 成分(短语结构)语法
语法是简单的,因为它是三艺中的第一部分。三艺和四艺是中世纪学校的两大主要学科,划分了七门自由艺术的学习内容。三艺包括语法、逻辑和修辞;四艺包括算术、几何学、音乐和天文学。神学、法学和医学不被视为自由艺术,因为它们分别由教皇、国王和医师公会控制。因此,语法是三艺中最简单的部分。在学习的起始阶段,学生们被教导将单词分为 8 种基本的句法类别,这一思想可以追溯到公元前二世纪的狄俄尼修斯·特拉克斯:名词、动词、分词、冠词、代词、介词、副词和连词。类别的概念源自亚里士多德的《工具论》第一卷³。印欧语言的基本名词-动词结构早在更早的时候就已经被注意到,但亚里士多德明确阐述了句法-语义难题:语言中的词类如何反映世界中事物的类别? 长时间以来,将单词划分为类别一直是所有学习的起点。随着语言理解的演变,其结构逐渐成为学习的入口。
形式语法和语言将在接下来的几个展示中定义。它们展示了工作原理。如果你不需要详细信息,可以跳过它们,直接进入主要内容。符号将在注释中解释⁴。
短语结构语法理论的思想是,从一个作为终结符集合的词汇表𝛴开始,并指定一个生成所需的良好构句集合𝓛的语法𝛤。
语法如何生成句子。 最常见的句子形式是“主语爱宾语”。语法教材中最常见的一句话出现在下图左侧:
英语语言的基本事实与 Dall-E 对句子“无色的绿色想法愤怒地睡觉”的看法
上图展示了该句的成分树。该句由一个名词短语(NP)和一个动词短语(VP)组成,两者尽可能简单:名词短语是表示主语的名词,动词短语是一个及物动词,后接另一个表示宾语的名词短语。“主语-宾语”的术语对不同的人有不同的理解。各种各样的想法。如果即使是最简单的语法结构也能引发广泛的语义联想,那么就不存在纯粹的语法示例。每一个词序都有其含义,而含义是一个过程,始终在变化,始终可以分解。为了展示语法与语义的分离,乔姆斯基构造了一个(语法上)正确但(语义上)毫无意义的句子,如上图右侧的 Dall-E 所示。这个例子作为证据表明,语法正确并不意味着语义可解释。但也有一个完整的传统,通过诗歌、故事和插图为这个句子赋予意义。上面 Dall-E 的贡献就是其中较为简单的一个。
马克思主义语言学与工程学。 若想更深入地探讨语法与语义之间的分界线,可以考虑格劳乔·马克思在电影《动物饼干》中的一句话:“有一天早上我在睡衣里打死了一头大象”,这句话中的歧义。
youtu.be/NfN_gcjGoJo?si=AucqaRQvvfoAlVIo
该主张存在歧义,因为它允许两种语法分析:
两者都是使用相同的语法推导出来的:
尽管这两种分析在语法上都是正确的,但只有一种在语义上是现实的,而另一种则是个笑话。为了植入笑点,格劳乔通过添加“我不知道他是怎么进入我的睡衣的。” 将他的主张与第二种解释联系起来。笑点来自于从语法模糊性到语义不可能性的意外转折。关于“无色的绿色想法”和“我睡衣里的大象”的句子说明了语法与语义表面上出现的偏离过程,这一过程在语言学中被研究,并在喜剧中得到应用。
形式文法的历史。 形式文法中使用的符号::= 是这种规则曾被认为是单向方程的遗迹。上述形式文法定义中的规则(1)应理解为类似于:“每当你看到αβγ时,你可以将其重写为αδγ,但不能反过来”。由这种单向方程系统所呈现的代数理论由阿克塞尔·图厄(Axel Thue)在 20 世纪初研究。埃米尔·波斯特(Emil Post)在 1920 年代使用这种系统研究字符串重写,以构造我们现在称之为程序的东西,比哥德尔(Gödel)和图灵(Turing)提出编程思想早了 10 多年。在 1940 年代,波斯特证明了他的字符串重写系统与图灵、哥德尔和丘奇(Church)的计算模型同样强大,而这些计算模型此时已出现。诺姆·乔姆斯基(Noam Chomsky)在 1950 年代提出形式文法作为一般语言学的主要工具,基于波斯特的工作,并受计算理论的启发,迅速扩展并证明了当时一些最深刻的结果。尽管自然语言的可用文法仍然需要大量关于转换、附加条件、绑定等的额外工作,但乔姆斯基当时分类的简单形式文法自此以来一直是指定编程语言的主要工具。
形式文法和语言的层次结构。 乔姆斯基通过对生成语言的语法规则施加约束,定义了下图所显示的语言嵌套结构。
乔姆斯基的形式文法和语言层次结构
约束条件总结如下表所示。我们可以说:
以下是来自每个文法家族的一些示例⁵,以及典型的推导树和语言:
典型文法的生成树及其诱导语言
它真的是我大脑里的工作方式吗? 现实的科学模型通常并不声称它们就是现实。物理学家并不声称量子态由用来模拟它们的密度矩阵构成。文法只是语言的计算模型,诞生于计算理论的早期。短语结构文法曾试图用计算的术语来解释语言。如今,即使是编程语言,也不再以这种方式运作了。它只是一个模型。
然而,当涉及到心理过程的心理模型时,现实与其模型之间的划分变得微妙。它们可以相互反映并相互影响。计算机的计算模型使得计算机可以模拟自身。语言可以在自身内部进行建模,而这种模型可以与它所模拟的过程相似。它能有多接近?
2.2. 依赖文法
依赖语法更接近于捕捉句子生成的过程。语法依赖是一种句子中单词之间的关系。它将一个中心词与一组(有序的!)依赖项关联。句子的生成是随着给定中心词的依赖项被选择,或给定依赖项的中心词被选择的过程。选择是按照单词出现的顺序进行的。下面是以 Groucho 的“大象”句子为例的工作方式:
展开依赖关系。 代词“I”首先出现,它只能作为某个动词的依赖构成一个句子。一旦动词“shot”被发出,它就被选为该依赖关系的中心词。然后,如果动词“shot”作为不及物动词使用,句子就可以结束。如果它作为及物动词使用,则需要选择动作的宾语作为它的另一个依赖项。Groucho 选择了名词“elephant”。英语语法要求该名词也是另一个依赖关系的中心词,带有一个冠词作为其依赖项。由于冠词必须位于名词之前,因此在选择“an”或“the”作为依赖项之前,名词“elephant”是不会被发出的。当“我射杀了一只大象”这些词被说出(或接收)后,依然有多个选择要做:句子可以在没有进一步依赖项的情况下结束,或者可以将一个依赖项添加到中心词“shot”,或者可以将其添加到中心词“elephant”。后两种句法选择对应不同的语义意义,产生歧义。如果介词短语“in my pajamas”是“shot”这个中心词的句法依赖项,那么主语“I”是在穿着睡衣的情况下开枪的。如果介词短语是“elephant”这个中心词的句法依赖项,那么射击的对象是穿着睡衣的。两种依赖分析如图所示,图上方画有相应的构成分析。
依赖短语“in my pajamas”的中心词是介词“in”,它的依赖项是名词“pajamas”,而“pajamas”的依赖项是所有格“my”。之后,讲者必须再次选择是结束句子,还是添加另一个依赖短语,比如“while sleeping furiously”,这会打开同样的两个句法依赖和语义歧义的选择。令所有人宽慰的是,讲者选择了结束句子。
依赖关系是句法关系还是语义关系? 依赖关系存在的要求通常是句法性的。例如,为了构成一个句子,一个起始名词通常是动词的依赖项。但是,选择某个特定的依赖项或主语分配则主要是语义性的:比如我射杀的是一只大象还是一块交通标志。依赖于大象的冠词的选择取决于上下文,可能是远程的:是否已确定了某只特定的大象。如果没有确定,那么不定冠词an的形式就是通过句法确定的,而非语义决定的。
所以,以上问题的答案似乎暗示,词语之间的关系划分为句法和语义过于简单化,因为语言的这两个方面并非独立,而是紧密相连,甚至是不可分割的。
3. 句法即类型
3.1. 句法类型检查
在计算中,类型检查是一种基本的错误检测机制:例如,算术操作的输入被检查为类型𝖨𝗇𝗍𝖾𝗀𝖾𝗋,数据库中的出生日期被检查为月份字段的类型为𝖬𝗈𝗇𝗍𝗁,其值可以是整数 1, 2, …, 12,如果某人的出生月份输入为 101,类型检查就会捕捉到这个错误。类型允许程序员通过约束可以处理的数据来确保程序的正确执行⁶。
语言处理同样基于类型检查,但检查的是句法类型。例如,一个<动词>的依赖类型必须是<名词短语>,如果我尝试用动词和动词构成一个句子,语言处理器就会捕捉到这个错误。就像类型𝖨𝗇𝗍𝖾𝗀𝖾𝗋限制算术操作的输入为整数一样,句法类型<动词>将句子中的谓词限制为动词,句法类型<名词短语>则限制为名词或需要进一步类型检查的类型。无论如何,句法类型检查是一种错误检测机制,就像在计算中一样。而且,类型约束甚至允许错误修正。例如,如果你听到类似“John lo℥∼ Mary”的话,没有类型约束,你将不得不考虑超过 3000 个以“lo”开头的英语单词作为可能的补全。而在句法约束下,你丢失的单词必须是第三人称单数的及物动词,你的选择就减少到了“lobs”,“locks”,“logs”,……可能还有“loathes”,当然,也包括“loves”。
3.2. 解析与类型
语法规则因此与程序中的类型声明有关,如下所示:
在上面列出的语法中,经过格劳乔大象句子的两次分析后,左边列出的终结规则是基本类型声明,而右边的非终结规则是类型构造器,用于从简单类型构造复合类型。因此,成分分析树展示了被分析句子的类型结构。句子的单词作为叶节点出现,而内部树节点是类型。分支节点是复合类型,非分支节点是基本类型。成分分析是类型化的。
另一方面,依赖解析做了一件奇怪的事情:它们通过连接头项与其依赖项的成分类型传递依赖关系,然后绕过这些类型,直接将头项与其依赖项连接。这正是上面的依赖图所展示的内容。依赖解析将句法类型简化为项依赖。
但是,只有记录了纯粹项依赖的类型才能简化为项依赖。大象句子的两种依赖解析大概是这样的:
由带伴随的句法类型编码的项依赖作为注解
两个句子副本下的表达式是通过依赖解析捕捉到的句法类型。它们通过将参考变量 x, y, … 等,与其上划线的左伴随和下划线的右伴随进行配对生成。这些句法类型构成了前群,这是一种由吉姆·兰贝克(Jim Lambek)在 1990 年代末引入的代数结构,旨在简化他关于类别语法的句法演算。他在 1950 年代末引入了类别语法,旨在探索艾季基维奇(Ajdukiewicz)1930 年代的句法连接决策程序,以及巴尔-希勒尔(Bar-Hillel)1950 年代初期的准算术,这两者都基于回溯到胡塞尔(Husserl)“逻辑研究”中的基于参考的意义逻辑。类别语法在随后的几十年中得到了广泛研究。我们仅接触前群,作为一个踏脚石。
3.3. 前群语法
前群是一个带有左伴随和右伴随的有序单群。有序单群是一个单群,其中底层集合是有序的,并且单群乘积是单调的。
如果你知道这是什么意思,可以跳过这一部分。如果你不需要知道它是如何工作的,也可以跳过,因为主要思想应该会在你继续阅读时自然显现出来。为了以防万一,下面是详细内容。
容易证明,所有前群的元素作为带伴随的有序单群都满足以下声明:
示例。 由任意部分有序集(poset)生成的自由 pregroup,由由该有序集元素及其附加类型所形成的元组构成。单群操作是元组的连接。顺序是逐点从生成的有序集中提升的,并且(最重要的是)通过附加类型定义中的顺序条款进行扩展。对于一个非自由示例,考虑从整数到整数的单调映射的单群。再次与逐点顺序结合时,单调映射形成一个 pregroup,因为每一个在两侧都有界限的整数集合都包含其交集和并集,因此每一个单调映射都必须保持这些性质。这允许构造附加类型。
这就是为什么 pregroups 有助于理解语言的原因。
作为类型检查的解析。 为了检查给定短语的语义正确性,首先为短语中的每个单词分配一个 pregroup 元素作为其句法类型。短语的类型是它的单词类型在 pregroup 中的积。若短语的句法类型在 pregroup 单位𝜄的上界范围内,则该短语是一个结构良好的句子。换句话说,我们计算给定短语的句法类型S,当且仅当S≤𝜄时,它才是一个句子。该计算可以简化为通过绘制弧线将每种类型x与一个附加类型连接,不论是左侧还是右侧,并将它们配对,使得每对类型都低于𝜄。如果这些弧线排列良好⁷,消除那些根据上述附加定义低于𝜄的相邻链接对,并用单位替代它们,就会使得其他附加对相邻并准备被消除。如果短语是一个句子,按照这种方式进行处理,会将其类型简化为单位。由于这一过程是单调不减的,这证明了原始类型被𝜄单位上界限制。如果这些类型无法通过连接和消除来匹配,那么该短语就不是一个句子。类型实际上告诉我们它是什么类型的短语。
我们显然跳过了许多细节,其中一些是很重要的。在实践中,句子的主语通过一个类型变量S进行标注,该变量不会被释放,并且它的连接线不会弯曲到句子中的其他类型,而是直接指向外部。这条连接线可以解释为对另一个句子的引用。通过将一对句子的S-变量链接起来,并进行配对,例如,问题和回答,人们可以实现一种 pregroup 版本的语篇句法。更进一步地,通过配对消息和配对,比如认证协议中的挑战和响应,便可以实现协议形式主义的 pregroup 版本。我们稍后会回到这个话题。
虽然它们显然与依赖关系相关,但预组耦合通常偏离这些依赖关系。在句子层面,这是因为词典中属于相同语法类型的词汇应该被分配相同的预组类型。兰贝克的想法是,即使在成分语法中,相同类型的短语也应该接受相同的预组类型。这个要求是否合理且有利仍是争议问题。这里唯一重要的观点是,语法是类型化的⁸。
4. 超越句子
4.1. 为什么我们要造句?
为什么我们不把词语像网络路由器传输数据包一样流动?为什么我们不能通过增加更多的词汇来逼近我们想说的内容,就像数字通过增加更多的位数来逼近空间中的点一样?
旧的答案是:“我们通过句子来喘口气”。当我们完成一个句子时,我们释放其词语之间的依赖关系。没有这一点,依赖关系会积累,你一次只能在脑中保持有限的线程。呼吸使引用关系不会缠结。
练习。我们造长句有多种原因和目的。下面提供了一句话的示例⁹。试着将其拆分成更短的句子。通过这种操作获得了什么,失去了什么?请问聊天机器人做一下。
指代是一种出现在句子内或句子间的句法模式。在修辞和诗歌中,它是一种通过重复同一短语来加强论点或串联引用的修辞手法。在 ChatGPT 看来,它之所以有效,是因为诗句的节奏与意义模式相呼应:
每个词中,生命的节奏跳动,
在每个真理中,生命的声音发出。
在每个梦中,生命的愿景寻求,
每个咒骂中,生命的复仇升起。
每个笑声中,生命的节拍接近,
每个停顿中,生命的声音退去。
句法划分反映语义划分。句子语法是通过充放句法依赖关系来传递语义引用的学科。
4.2. 语言的表达与网络层次
语言流被组织成词语、句子、段落、章节、书籍、图书馆、文学;说话者讲述故事,发表演讲,维持对话,遵循约定,遵守协议。计算机将言语简化为推文并扩展到聊天机器人。
语言表达的分层是沟通通道分层的一个实例。人工语言也经历了相同的分层过程。互联网协议栈是另一个实例。
自然语言是有层次的
编程语言由…组成
网络通道堆叠着
通信通道是分层的,因为信息承载体是层叠实现的。层次化的交互架构在生物体中普遍存在,在它们之间的通信网络中也存在,在人类开发的所有语言中都能看到。我们研究过的类似语法类型结构的引用耦合机制在各个层级中都会出现。句子语法的预组结构在简单话语的问答结构和基本网络协议中的 SYN-ACK 模式中都有体现。所有种类的协议中都会出现紧密相关的结构,无论是为了规范网络功能、确保交互安全,还是为了社交、政治和经济机制而建立的。下图展示了一个简单的 2 因子认证协议的高级视图,作为基本的 电缆空间¹⁰:
夏威夷大学的 Laulima 协议作为电缆空间
这里是相同的协议,将电缆交互视为类型的伴随对:
Laulima 协议作为一种语法类型
相应的交互由相应的序列号标记。结果是:
-
自然语言对话,
-
软件系统架构,
-
安全性和网络协议
具有关键特征。人们很容易将它们视为所有通信过程共享的高级深层语法的产物。这样的语法可以合理地从乔姆斯基理论中假设的天赋能力中产生,或者从信息处理的物理和逻辑法则中衍生出来。
5. 超越语法
我们已经看到语法类型如何支持语义信息的传递。格劳乔的象句已经将语法和语义歧义互相反馈。
但如果语法类型与语义分配相互作用,那么普遍接受的将语法分析仅限于句子的限制就无法得到证实,因为语义歧义无法在句子层面上解决。格劳乔已经证明了这一点。
5.1. 语义上下文敏感性
考虑一下这个句子
约翰说他生病了并起身准备离开。
添加上下文会改变其含义:
马克倒在床上。
约翰说他生病了并起身准备离开。
对大多数人来说,“他生病了”现在指的是马克。注意,在“[他]起身准备离开”中的沉默“他”仍然与约翰绑定。或者看
很少有教授来参加派对并度过了愉快的时光。
如果我们将句子拆分成两部分并扩展,它的含义并不会发生显著变化:
由于开会晚了,来参加派对的教授很少。他们度过了愉快的时光。
就像约翰和马克的例子中一样,一个上下文改变了语义绑定,这次是“它”的绑定:
在 5 点钟有一个系部会议。由于开会晚了,来参加派对的教授很少。他们度过了愉快的时光。
但这一次,添加一个将主语“他们”以不同方式绑定的首句,可能会改变最后一句中“他们”的含义:
他们邀请了教授。下午五点有一个系务会议。由于会议开始晚,来参加派对的教授很少。他们度过了愉快的时光。
现在故事是学生们度过了愉快的时光——这些学生从未被明确提及!他们的存在只是通过关于教授生活一般背景知识推导出来的 😉
5.2. 句法上下文敏感性
在自然语言的句法层面,由形式语法生成的句子,证明上下文敏感性等同于找到包含一些已知需要上下文敏感语法的模式的语言,如任意字母 a,b,c∈𝛴 和任意数字 n 的 aⁿbⁿcⁿ,或任意单词 w∈𝛴* 的 ww,www,或 wwww。由于人们不太可能互相说像 aⁿbⁿcⁿ 这样的句子,这项任务就变成了找到需要重复单词构造形式 ww,www 等的语言。寻找这种例子的竞争变得相当激烈。
由于一个词汇有限的语言有一个有限的数字词汇,在某个时刻你必须说类似“千万万”的话,假设“千万万”是一个由单一词表示的最大数字。但根据上下文敏感性竞赛裁判的决定,数字不算在内。
然后有人发现,在中非的班巴拉语言中,表示“任何狗”的构造形式是“狗狗”。接着又有人注意到荷兰语中的上下文敏感嵌套现象,但并非所有人都认同。最终,大多数人认为瑞士德语是一种明确的上下文敏感语言,关于句法上下文敏感性的争论也随之平息。事后看来,这场争论具有明显的神学辩论特征。关于多少天使可以站在针尖上的主要问题是,天使通常不会待在针上。关于句法上下文敏感性的主要问题是,上下文从来不是纯粹的句法。
5.3. 沟通是共享语义上下文的过程
乔姆斯基指出,一旦他定义了“上下文敏感性”这一概念,自然语言就应该被视为上下文敏感的。将语言模型限制为句法,并将句法限制为句子,使得证明他的观察结果变得非常棘手。
但现在句法上下文的神学问题已被抛在脑后,语言模型摆在我们面前,等待被理解,问题随之而来:上下文究竟是如何处理的?我们是如何做到的,聊天机器人又是如何做到的?我们都将大量上下文存储在哪里?一部小说从第一句开始建立其上下文,并在 800 页后提及它。语言模型如何找到这种引用的目标?它不能维持任何事物之间的相互引用。你如何选择记住什么?
语义依赖于遥远上下文的问题一直是自然语言处理的核心问题之一。我们目前目睹的自然语言处理的进展,在很大程度上源自于解决这一问题的进展。为了了解这一挑战,考虑以下段落¹¹:
不稳定地,霍尔姆斯从驳船上走了下来。莫里亚蒂正转身离去。
沿着拖曳小道进入了雾中。霍尔姆斯追了上去。`把它还给我',他喊道。莫里亚蒂转身笑了笑。他张开了手,手中拿着钥匙。
一小块金属掉到了小路上。霍尔姆斯伸手去捡,但
莫里亚蒂太快了。只需微微一动脚,他便把钥匙插入了锁中。
如果你现在还不明白刚才发生了什么,说明你并不孤单。没有足够的提示,当前可用的聊天机器人似乎无法做出正确的解释¹²。在接下来的部分,我们将看到如何生成这些上下文,且包含更大的上下文。之后,我们将准备好解释它们是如何被处理的。
属性
正如文中提到的,代指的诗句“在每一个……生命中的”是由 ChatGPT 创作的,而句子“无色的绿色想法猛烈地沉睡”的插图是由 Dall-E 生成的。所有其他图形均由作者创作。
注释
¹大多数人在讲相同语言时仍然不能相互理解,这一事实与同种生物大多数成员不繁殖而是通过复杂的仪式选择配偶的事实相呼应。
²早期的语言学家(洪堡特、博阿斯、萨皮尔、沃尔夫)主要关注通过理解不同语言(德语、希伯来语、英语、霍比语、纳瓦胡语……)来理解不同的世界观(Weltanschauung, Weltanzicht)。
³三学科的划分呼应了Organon的组织结构,其中第一本书专门讨论了类别,接下来的三本书讨论了逻辑,最后两本书讨论了主题性论证,进而进入修辞学。
⁴对于任意集合A,写作A**表示所有n元组 𝛼={a1,a2,…,an}* 从A中选出的集合,适用于所有n = 0,1,…以及任意从A中选出的a1,a2,…,an。由于n可以是 0,A**包括空元组,写作<>。设所有标签的集合为𝛬 = 𝛴∪𝛯,则规则集合是一个有限的二元关系[::=] ⊆ 𝛬×𝛬*,通过列出(1)得到。
⁵乔姆斯基的Type-x''术语与
句法类型''术语无关。许多语言学家更倾向于使用句法类别''这一术语。不过,
类别''这个词同时在数学中有着完全不同的意义,并且在语言学中越来越多地被应用。
⁶有关类型的数学理论的历史和逻辑背景,请参阅《程序作为图示》一书的第一章。
⁷如果弧线没有很好地嵌套,就像荷兰语句法的情况那样,那么过程会更复杂,但我们不会深入探讨这个问题。
⁸关于基于预群的句法分析的更多细节,请参阅吉姆·兰贝克的书籍《从词到句》。关于逻辑和数学背景,请参阅《兰贝克预群是弗罗贝纽斯蜘蛛》。
⁹“那天晚上,他梦见自己在一片高原上,春雨滋养了草地和野花,野花从地里冒出来,开满了蓝色和黄色,一眼望不到边。在梦中,他和马群一起奔跑,在梦中他自己也能和马一起奔跑,马群在草原上追逐年轻的母马和小马驹,阳光下它们的深棕色和栗色毛发闪闪发光,年轻的小马驹与母马一起奔跑,踩踏着花朵,花朵在阳光下变得模糊,像金粉一样的花粉悬浮在空中,他们奔跑着,他和那些马穿越高高的台地,脚下的土地在马蹄的奔腾声中回响,他们奔跑、变换、流动,鬃毛和尾巴如泡沫般随风飘扬,那片高远的天地里什么都没有,只有他们在共鸣中移动,这共鸣如同音乐在他们之间流淌,没有任何一匹马、驹子或母马感到恐惧,他们在那共鸣中奔跑,那共鸣就是这个世界,它是无法言说的,只有被赞美。” — 科马克·麦卡锡,《所有美丽的马》
¹⁰绳索空间是分析安全协议的一种简单形式。
¹¹这一段落是亚瑟·柯南·道尔的短篇小说《最后的谜题》中的情节变体。
¹² 关于注意力跨度的关键点:
A/B 测试的前后合成控制样本
学习一种简单的方法,使用线性回归为您的 A/B 测试创建一个合成控制样本
·发表于 Towards Data Science ·阅读时间 11 分钟 ·2024 年 12 月 19 日
--
前后测试 | 图片由 AI 生成。Meta Llama,2024. meta.ai
介绍
A/B 测试非常强大。我喜欢这种实验,因为它使我们能够比较不同结果,并确定某些事物是否表现得比其他事物更好。
A/B 测试有一种特定的类型,它加入了 时间 组件,即 前后 A/B 测试。在该测试中,比较的是某一主题在干预前后的情况。
让我们将之前的句子翻译为一个实际的例子。
一家公司想知道广告是否能推动销售增长,因此他们可以将该广告展示给实验组,并将结果与未看到广告的对照组进行比较。广告前后的差异将指示干预是否有效。
现在,有时候在干预之前无法提前计划并进行控制组和实验组的分配。
这时,合成控制样本将变得有用。通过一些统计学和机器学习技术,可以模拟如果没有干预样本会发生什么…
实践中的合成数据:Shopify 案例研究
使用 30k 条记录数据集测试新的 Snowflake 功能
·发表于Towards Data Science ·13 分钟阅读·2024 年 12 月 10 日
--
由 DALL·E 根据作者的提示生成的图像
在处理数据时,我越来越频繁地遇到同样的问题。一方面,我们对数据隐私和保密性的要求日益增加;另一方面——我们需要快速做出数据驱动的决策。再加上现代商业的现实:自由职业者、顾问和短期项目。
作为决策者,我面临一个两难:我现在需要分析,内部团队已经超负荷工作,而且我不能随便把机密数据交给每一个外部分析师。
这就是合成数据的作用所在。
等等——我不想再写一篇关于合成数据是什么的理论文章了,网上已经有足够多了。相反,我将向你展示一个具体的对比:30,000 笔真实的 Shopify 交易与它们的合成数据对比。
我到底检查了什么?
-
合成数据能多忠实地反映真实趋势?
-
最大的差异在哪里?
-
我们什么时候可以信任合成数据?又在什么情况下应该谨慎?
这不会是另一篇“如何生成合成数据”的指南(虽然我也会展示代码)。我关注的是真正重要的——这些数据是否真正有用,它的局限性是什么。
我是一个实战派——少说理论,更多具体细节。让我们开始吧。
数据概览
在测试合成数据时,你需要一个坚实的参考点。在我们的案例中,我们使用的是来自一家成长中的电子商务企业的真实交易数据:
-
30,000 笔交易,跨越 6 年
-
年年增长趋势明显
-
销售月份的高低波动混合
-
多样化的地理分布,其中一个市场占主导地位
所有图表由作者创建,使用他自己的 R 代码
对于实际测试,我专注于交易级数据,如订单金额、日期和基本地理信息。大多数评估只需要基本的业务信息,不涉及个人或产品的详细信息。
该过程很简单:导出原始 Shopify 数据,分析后仅保留最重要的信息,在 Snowflake 中生成合成数据,然后将这两个数据集并排比较。可以将其视为生成“数字双胞胎”业务数据,具有可比的趋势,但完全匿名化。
[技术说明:如果你对详细的数据准备过程感兴趣,包括 R 代码和 Snowflake 设置,请查看本文末尾的附录。]
月度收入分析
任何合成数据集的首要测试是它能多好地捕捉核心业务指标。让我们从月度收入开始——这无疑是任何业务中最重要的指标之一(肯定排在前三名)。
从原始趋势(图 1)来看,两个数据集都遵循类似的模式:多年持续增长,并伴有季节性波动。合成数据很好地捕捉了总体趋势,包括业务的增长轨迹。然而,当我们深入探讨差异时,一些有趣的模式浮现出来。
为了量化这些差异,我计算了一个月度变化值:
Δ % = (Synthetic - Shopify) / Shopify
从图中可以看到,月度收入变化值是有波动的——有时原始数据更大,有时是合成数据更大。但这些柱状图看起来是对称的,而且随着时间的推移,差异变得越来越小。我增加了每月记录数(交易量),也许它有一些影响?让我们深入探讨一下。
这些变化值确实相当平衡,如果我们看累计收入曲线,它们非常吻合,没有大的波动。我跳过这个图表。
样本量的影响
变化值变小了,我们直觉上觉得是由于记录数的增加。让我们检查一下——下一个图表显示了月度收入变化值的绝对值与每月记录数的关系。虽然记录数随着时间的推移在增长,但 X 轴实际上并非时间——而是记录数。
这些变化值(绝对值)确实在减少,因为每月记录数增多——正如我们预期的那样。但还有一件事,相当有趣,而且不那么显而易见,至少在初次看来。每月记录数超过约 500 条后,变化值不再进一步下降,它们保持在(平均值)大致相同的水平。
尽管这个具体数字来源于我们的数据集,并可能因不同的商业类型或数据结构而有所变化,但模式本身是重要的:存在一个阈值,在这个阈值下,合成数据的稳定性显著提高。在这个阈值以下,我们看到较大的方差;而超过阈值后,差异稳定下来,但并未完全消失——合成数据根据设计保持一定的变化,这实际上有助于隐私保护。
存在噪声,这使得每月的数值变得随机化,尤其在样本较大时。所有这些都同时在较高的汇总(年度或累计)数据上保持一致性,并且能够很好地再现总体趋势。
如果能看到其他指标和数据集的类似图表,将会非常有趣。
我们已经知道收入增量依赖于记录数,但这仅仅是因为某个月份的记录数越多,合成数据的收入就越高吗?让我们来探讨一下…
因此,我们希望检查收入增量如何依赖于记录数增量。我们所说的增量是指 Synthetic-Shopify 之间的差异,无论是月收入还是月记录数。
下图展示了这种关系。存在一定的(轻微的)相关性——如果每月记录数在 Synthetic 和 Shopify 之间差异显著,或反之(高的增量值),收入的增量也会随之变化。但这远不是简单的线性关系——其中也存在额外的噪声。
维度分析
在生成合成数据时,我们通常不仅需要保持整体指标,还需要保持它们在不同维度上的分布,比如地理分布。我在我们的测试数据集中保留了国家和州列,以便查看合成数据如何处理维度分析。
结果揭示了两个重要方面:
-
合成数据的可靠性在很大程度上依赖于每个维度内的样本量
-
维度之间的依赖关系未被保留
查看按国家划分的收入:
对于有成千上万交易的主导市场,合成数据提供了可靠的表示——实际和合成数据集之间的收入总额是可以比较的。然而,对于交易较少的国家,差异变得显著。
关于维度关系的一个关键观察:在原始数据集中,州信息仅出现在美国交易中,其他国家的州信息为空。然而,在合成数据中,这种关系丧失——我们看到在国家和州列中随机生成的值,包括分配给其他国家的州,而非美国。这突显了一个重要的限制:合成数据生成并未保持维度之间的逻辑关系。
然而,确实有一种实用的方法可以克服这个国家-州依赖性问题。在生成合成数据之前,我们可以通过将国家和州合并为一个维度来预处理输入(例如,“US-California”,“US-New York”,而对于非美国交易,只保留“Germany”或“France”)。这一简单的预处理步骤将保留州是美国特有的业务逻辑,并防止在合成数据中生成无效的国家-州组合。
这有重要的实践意义:
-
合成数据在高流量细分市场中效果良好
-
在分析较小细分市场时要小心
-
在得出结论之前,务必检查样本量
-
要注意,维度之间的逻辑关系可能会丢失,可以考虑对某些列进行预聚合处理
-
如果维度完整性至关重要,考虑进行额外的数据验证
交易价值分布
这项分析中最有趣的发现之一来自于检查交易价值分布。逐年查看这些分布揭示了合成数据的优点和局限性。
原始的 Shopify 数据展示了你在电子商务中通常会遇到的情况:高度不对称的分布,向更高值的长尾,并且有与畅销单品交易相对应的显著峰值,展现了明显的畅销产品模式。
合成数据讲述了一个有趣的故事:它很好地保持了分布的整体形状,但来自畅销产品的显著峰值被平滑化了。分布变得更“理论化”,失去了一些现实世界的特征。
这种平滑效应不一定是坏事。事实上,在某些情况下,它可能是更可取的:
-
对于一般的商业建模和预测
-
当你想避免过拟合于特定的产品模式时
-
如果你关注的是潜在的趋势,而不是具体的产品效果
然而,如果你特别关注畅销产品分析或单一产品交易模式,你需要考虑到合成数据的这个局限性。
知道我们的目标是产品分析,我们将会以不同的方式准备原始数据集。
为了量化合成数据与真实分布的匹配程度,我们将在下一节中讨论统计验证。
统计验证(K-S 检验)
让我们通过科尔莫戈罗夫-斯米尔诺夫检验来验证我们的观察结果——这是一种用于比较两个分布的标准统计方法。
这些发现是积极的,但这些数字在实践中意味着什么呢?科尔莫戈罗夫-斯米尔诺夫检验比较了两个分布并返回了两个重要的指标:D = 0.012201(越小越好,0 表示分布完全相同),p 值 = 0.0283(低于通常的 0.05 水平,表示统计上有显著差异)。
虽然 p 值显示分布之间存在一定的变化,但非常低的 D 统计量(接近 0)验证了图表的发现:中间部分几乎完美匹配,只有在极端值处有轻微差异。合成数据捕捉了关键的模式,同时保持足够的方差以确保匿名性,使其适用于商业分析。
在实际操作中,这意味着:
-
合成数据在交易值的最重要中间范围内提供了非常匹配的结果
-
该匹配在我们拥有最多数据点的地方特别强
-
差异主要出现在边缘案例中,这是预期的,甚至从隐私角度来看是可取的
-
统计验证确认了我们从分布图中获得的视觉观察结果
这种统计验证在决定是否使用合成数据进行任何特定分析之前至关重要。在我们的案例中,结果表明合成数据集对于大多数商业分析用途是可靠的,尤其是在关注典型交易模式而非极端值时。
结论
让我们总结一下从真实的 Shopify 交易到其合成对等物的过程。
整体业务趋势和模式得以保持,包括交易价值分布。尖峰已被平滑,结果呈现出更加理论化的分布,同时保持关键特征。
样本大小很重要,这是有意设计的。过于细化会产生噪音,同时确保保密性(当然还会移除所有个人身份信息)。
列之间的依赖关系未被保留(如国家-州),但有一个简单的解决办法,因此我认为这不是一个真正的问题。
了解生成的数据集将如何使用非常重要——我们预期进行什么样的分析,以便在重塑原始数据集时考虑到这一点。
合成数据集将非常适用于应用程序测试,但我们应该手动检查边缘案例,因为这些在生成过程中可能被遗漏。
在我们的 Shopify 案例中,合成数据在大多数商业分析场景中证明是足够可靠的,尤其是在处理较大的样本并专注于一般模式时,而不是特定的产品级别分析。
未来工作
本次分析专注于交易,作为一个关键指标和一个易于入手的案例。
我们可以继续进行产品分析,也可以探索多表场景。
同样,值得制定内部指南,说明如何使用合成数据,包括检查和限制。
附录:数据准备和方法论
你可以浏览这一部分,因为它非常技术性,涉及如何准备数据。
原始数据导出
我没有依赖预先汇总的 Shopify 报告,而是直接使用了原始交易数据。在 Alta Media,我们采用的是这种标准方法——我们更倾向于使用原始数据,以保持对分析过程的完全控制。
从 Shopify 导出的过程是直接的,但并非即时的:
-
从管理面板请求导出原始交易数据
-
等待包含下载链接的电子邮件
-
下载多个包含 CSV 数据的 ZIP 文件
数据重塑
我使用 R 进行探索性数据分析、处理和可视化。代码片段是 R 语言的,来自我的工作脚本,但当然也可以使用其他语言来实现相同的最终数据框。
初始数据集有几十列,所以第一步是选择与我们合成数据实验相关的列。
代码格式已调整,以便我们避免出现水平滚动条。
#-- 0\. libs
pacman::p_load(data.table, stringr, digest)
#-- 1.1 load data; the csv files are what we get as a
# full export from Shopify
xs1_dt <- fread(file = "shopify_raw/orders_export_1.csv")
xs2_dt <- fread(file = "shopify_raw/orders_export_2.csv")
xs3_dt <- fread(file = "shopify_raw/orders_export_3.csv")
#-- 1.2 check all columns, limit them to essential (for this analysis)
# and bind into one data.table
xs1_dt |> colnames()
# there are 79 columns in full export, so we select a subset,
# relevant for this analysis
sel_cols <- c(
"Name", "Email", "Paid at", "Fulfillment Status", "Accepts Marketing",
"Currency", "Subtotal",
"Lineitem quantity", "Lineitem name", "Lineitem price", "Lineitem sku",
"Discount Amount", "Billing Province", "Billing Country")
我们需要一个数据框,所以我们需要将三个文件合并。由于我们使用的是 data.table
包,语法非常简单。然后我们将合并后的数据集通过管道传递,去除不需要的列,仅保留选定的列。
xs_dt <- data.table::rbindlist(
l = list(xs1_dt, xs2_dt, xs3_dt),
use.names = T, fill = T, idcol = T) %>% .[, ..sel_cols]
我们还将列名更改为单个字符串,用下划线“_”替换空格——这样我们就不需要在 SQL 中处理额外的引号。
#-- 2\. data prep
#-- 2.1 replace spaces in column names, for easier handling
sel_cols_new <- sel_cols |>
stringr::str_replace(pattern = " ", replacement = "_")
setnames(xs_dt, old = sel_cols, new = sel_cols_new)
我还将交易 ID 从字符型“#1234”改为数值型“1234”。我创建了一个新列,这样我们就可以轻松比较转换是否按预期进行。
xs_dt[, `:=` (Transaction_id = stringr::str_remove(Name, pattern = "#") |>
as.integer())]
当然,你也可以覆盖它。
额外的实验
由于这是一个关于 Snowflake 合成数据生成的实验,我进行了额外的准备工作。Shopify 导出包含了实际客户的电子邮件,而这些电子邮件在 Snowflake 中生成合成数据时会被屏蔽,但我还是对它们进行了哈希处理。
所以我使用 MD5 对这些电子邮件进行了哈希处理,并创建了一个包含数字哈希值的额外列。这纯粹是实验性的——我想看看 Snowflake 如何处理不同类型的唯一标识符。
默认情况下,Snowflake 会将基于文本的唯一标识符进行屏蔽,因为它将其视为个人可识别信息。对于实际应用,我们希望删除任何可能识别客户的数据。
new_cols <- c("Email_hash", "e_number")
xs_dt[, (new_cols) := .(digest::digest(Email, algo = "md5"),
digest::digest2int(Email, seed = 0L)), .I]
我也很好奇逻辑列会如何处理,所以我更改了一个二进制列的类型,该列具有“yes/no”值。
#-- 2.3 change Accepts_Marketing to logical column
xs_dt[, `:=` (Accepts_Marketing_lgcl = fcase(
Accepts_Marketing == "yes", TRUE,
Accepts_Marketing == "no", FALSE,
default = NA))]
过滤交易记录
数据集包含每个项目的记录,而对于这项特定分析,我们只需要交易记录。
xs_dt[Transaction_id == 31023, .SD, .SDcols = c(
"Transaction_id", "Paid_at", "Currency", "Subtotal", "Discount_Amount",
"Lineitem_quantity", "Lineitem_price", "Billing_Country")]
最终的列子集并过滤支付总额的记录
trans_sel_cols <- c(
"Transaction_id", "Email_hash", "e_number", "Paid_at", "Subtotal",
"Currency", "Billing_Province", "Billing_Country",
"Fulfillment_Status", "Accepts_Marketing_lgcl")
xst_dt <- xs_dt[!is.na(Paid_at), ..trans_sel_cols]
导出数据集
一旦我们有了数据集,我们需要将其导出为 csv 文件。我导出了完整数据集,并且还生成了一个 5% 的样本,用于在 Snowflake 中的初步测试。
#-- full dataset
xst_dt |> fwrite(file = "data/transactions_a.csv")
#-- a 5% sample
xst_5pct_dt <- xst_dt[sample(.N, .N * .05)]
xst_5pct_dt |> fwrite(file = "data/transactions_a_5pct.csv")
还将其保存在 Rds 格式中,这样我就不需要重复所有准备步骤(这些步骤是脚本化的,因此无论如何都会在几秒钟内执行)。
#-- 3.3 save Rds file
list(xs_dt = xs_dt, xst_dt = xst_dt, xst_5pct_dt = xst_5pct_dt) |>
saveRDS(file = "data/xs_lst.Rds")
附录:在 Snowflake 中生成数据
一旦我们根据需求准备好数据集,生成其合成的“兄弟”数据集就变得简单了。需要上传数据、运行生成过程并导出结果。有关详细信息,请参考 Snowflake 指南。无论如何,我会在这里简要总结,以完整本文。
首先,我们需要做一些准备工作——角色、数据库和仓库。
USE ROLE ACCOUNTADMIN;
CREATE OR REPLACE ROLE data_engineer;
CREATE OR REPLACE DATABASE syndata_db;
CREATE OR REPLACE WAREHOUSE syndata_wh WITH
WAREHOUSE_SIZE = 'MEDIUM'
WAREHOUSE_TYPE = 'SNOWPARK-OPTIMIZED';
GRANT OWNERSHIP ON DATABASE syndata_db TO ROLE data_engineer;
GRANT USAGE ON WAREHOUSE syndata_wh TO ROLE data_engineer;
GRANT ROLE data_engineer TO USER "PIOTR";
USE ROLE data_engineer;
如果尚未定义,则创建模式和阶段。
CREATE SCHEMA syndata_db.experimental;
CREATE STAGE syn_upload
DIRECTORY = ( ENABLE = true )
COMMENT = 'import files';
上传 csv 文件到阶段,然后将它们导入到表中。
然后,生成合成数据。我喜欢先做一个小的“试点”,比如 5%的记录,用来初步检查是否可以通过。这是一个节省时间的办法(也节省成本),尤其是在更复杂的情况下,我们可能需要进行一些 SQL 调整。在这种情况下,它更多的是形式化操作。
-- generate synthetic
-- small file, 5% records
call snowflake.data_privacy.generate_synthetic_data({
'datasets':[
{
'input_table': 'syndata_db.experimental.transactions_a_5pct',
'output_table': 'syndata_db.experimental.transactions_a_5pct_synth'
}
],
'replace_output_tables':TRUE
});
检查我们得到的结果是很有必要的——直接在 Snowflake 中检查表格。
然后运行完整的数据集。
-- large file, all records
call snowflake.data_privacy.generate_synthetic_data({
'datasets':[
{
'input_table': 'syndata_db.experimental.transactions_a',
'output_table': 'syndata_db.experimental.transactions_a_synth'
}
],
'replace_output_tables':TRUE
});
执行时间是非线性的,对于完整的数据集,它比数据量所暗示的速度要快得多。
现在我们导出文件。
一些准备工作:
-- export files to unload stage
CREATE STAGE syn_unload
DIRECTORY = ( ENABLE = true )
COMMENT = 'export files';
CREATE OR REPLACE FILE FORMAT my_csv_unload_format
TYPE = 'CSV'
FIELD_DELIMITER = ','
FIELD_OPTIONALLY_ENCLOSED_BY = '"';
并且导出(小数据集和完整数据集):
COPY INTO @syn_unload/transactions_a_5pct_synth
FROM syndata_db.experimental.transactions_a_5pct_synth
FILE_FORMAT = my_csv_unload_format
HEADER = TRUE;
COPY INTO @syn_unload/transactions_a_synth
FROM syndata_db.experimental.transactions_a_synth
FILE_FORMAT = my_csv_unload_format
HEADER = TRUE;
所以现在我们有了原始的 Shopify 数据集和合成数据集。是时候进行分析、比较并绘制一些图表了。
附录:数据比较与图表
对于这项分析,我使用了 R 进行数据处理和可视化。然而,工具的选择并不是最重要的——关键是有一个系统化的数据准备和验证方法。无论你使用 R、Python 还是其他工具,重要的步骤始终是相同的:
-
清理并标准化输入数据
-
验证转化结果
-
创建可重复的分析
-
记录关键决策
详细的代码和可视化技巧实际上可以作为另一篇文章的主题。
如果你对实现的特定方面感兴趣,随时可以联系我。
合成数据:好、坏与未整理
关于使用合成数据进行 AI 训练的法律视角
·发表于 Towards Data Science ·阅读时间 9 分钟·2024 年 1 月 13 日
--
免责声明:这不是另一篇关于利弊的文章合集。
Anne Fehres 和 Luke Conroy & AI4Media / 更好的 AI 图像 / 人类负责繁重的数据搬运 / CC-BY 4.0
合成数据被认为是推动研究、产品开发,甚至支持依赖大量数据的整个商业模型的一个有前途的途径,特别是在真实数据稀缺、敏感或在法律上存在使用问题的情况下。然而,似乎在现有的法律文献中(主要由现有的概括性观点引起)对合成数据在避免法律雷区和改善 AI 模型方面的实际效用存在很多困惑。这种困惑导致了大量文章提到相同的利弊,但从不同的角度出发。而且,这些文章有时会交替提到相同的特点。因此,例如,虽然大多数人似乎都同意可扩展性和增强隐私是依赖合成数据的明确优势,但成本既被提到是一个优点[1](#_ftn1),也被提到是一个缺点[2](#_ftn2)。偏见也是如此,合成数据既可以作为减少偏见的一种手段[3](#_ftn3),但有时也会导致偏见的放大[4](#_ftn4)。这一切都取决于你偶然遇到的文章,以及这些文章中考虑的具体情况和场景。最糟糕的是,这些文章中的任何一个观点都不是错误的。
我们在这里不会尝试做相同的尝试。我们将更倾向于在当前混淆的使用、目的、概念、情况和情境之间画出一些界限,以至少有助于澄清现有的混乱。因此,我们暂时将事情复杂化,以便稍后澄清迷雾。然而,在此过程中,我们始终会关注隐私问题。(毕竟,这是我至少可以假装有一些答案并知道自己在说什么的事情。)
人工智能生成的合成数据
至少在法律背景下,合成数据最常被分析的优缺点与其在增强隐私保护方面的应用有关。然而,在这个背景下,合成数据似乎经常与匿名数据或假名数据进行比较,甚至等同于它们。这是令人遗憾的,因为假名化和匿名化在历史上一直存在误解和滥用。因此,关于这两者在隐私和《通用数据保护条例》(GDPR)背景下的含义,并没有真正的共识,这使得它们成为糟糕的参考点。但同时,因为合成数据是(或应该是)与这两者完全不同的独立概念,所以我们应该尽量避免将其与这两个误解的概念混淆。然而,尽管理想情况下我们应当远离这两个误解的概念,我们仍然会坚持使用它们。毕竟我们并非生活在真空中,所以不妨与之和解。
首先,合成数据绝不应该是假名数据。因为如果它是,那意味着我们没有很好地合成它。其次,它(通常)属于匿名数据的范畴。为什么?为了回答这个问题,我们先来探讨一下假名化的问题。
假名数据是指在没有使用附加信息的情况下,无法将数据归属于特定数据主体的数据,前提是这些附加信息被分开并安全保存。[ 5 ](#_ftn5) 需要记住两点:1. 重新识别的可能性仍然存在,2. 有人持有解密密钥。这就是为什么假名数据仍被视为个人数据,也是为什么合成数据不是假名数据。或者说,至少不应该是。
另一方面,匿名化是将个人数据(鼓点)变为匿名的过程。 [6] 这意味着匿名数据与任何自然人无关(根本不相关),或者曾经与某人相关,但现在已经被匿名化,无法再识别该人。 [7] 在这里有多个需要注意的地方。首先,匿名数据也可以指经过处理和匿名化的个人数据。其次,匿名数据不再用于识别其原始归属者(如果曾经属于某人)。至少在今天可用的技术和方法下无法做到。 [8] 第三,匿名数据也可以是完全非个人的数据,例如天气统计。
现在引入合成数据。合成数据是通过人工生成的数据,用来再现原始数据的特征和结构。[9] 我们可以将其视为原始数据的某种代理。在这个过程中,原始的真实数据不一定是个人数据,即使是个人数据,新的合成数据无论如何是匿名的,因为它是完全虚构的,并且不包含(或者至少不应该包含)其所基于的原始数据。这反过来使得合成仅仅是可能的匿名化技术之一。(现在暂时不讨论数据是否能够完全匿名化的问题。 [10] )但这仍然不意味着这两者总是完全相同的。
所有这一切都很重要,因为既然我们在谈论的应该是匿名数据,那么看起来整个隐私辩论实际上完全是没有意义的。因为匿名数据完全不在《通用数据保护条例》(GDPR)的范围之内。然而我们仍然在讨论它。而且我们这样做并没有错。
合成数据是隐私的朋友还是敌人?
我们在讨论合成数据与隐私之间的关系时,首先要考虑的事情是:为什么我们只是在某种程度上错了。合成数据必须通过生成得到,有时需要基于包含个人数据的数据集来生成。这现在(通常是,但不一定)是通过机器学习模型和深度学习算法来完成的。可能是因为我们在谈论处理和合成大量数据。而且,由于无论目的如何,识别模式和统计数据是机器学习模型天然擅长的(与大多数人类不同)。这反过来意味着,单单生成合成数据就已经是一个处理操作,从法律角度来看,这是相关的,特别是如果原始数据是个人数据的话。这也意味着,关于使用合成数据训练机器学习模型是否有益的常见论点和讨论,完全忽视了这个问题的两个关键方面。它们不仅通过将合成数据仅仅与隐私挂钩而过于简化问题,而且在大多数情况下忽略了仍然会有模型在处理个人数据以便进行合成。这些方面现在并不是被过度复杂化的边缘问题,相反,它们对于展开有关合成数据的(成熟的)对话非常关键,无论隐私是否在其中。
而且在谈论合成数据时,隐私问题往往没有被提及。再次指出,合成数据有其独立的生命。它作为一种技术和策略被应用于各种场合,包括当真实数据稀缺、敏感,或其使用涉及法律问题和不确定性的情形。例如,使用合成数据的一个重要目的(虽然在公众讨论中很少提及)就是在军事背景下训练人工智能系统。在那里,合成数据可以用于数据集多样化和数据属性精细控制的目的,这两个目的对于提升质量以及缩短训练周期在这一稀缺、难以获取且高度敏感的真实数据领域非常重要。 [11] 你很少在流行文献中看到这些类型的权衡,然而它们确实存在,并且同样重要,甚至在其他背景下也值得考虑。
另一方面,即使在隐私的背景下提到和关联合成数据,我们依然在过度概括事情,并混淆了两个不同的基本权利。因为,尽管隐私和数据保护在很大程度上有重叠,但它们不能作为同义词使用。而且,在讨论合成数据的背景下,尽管我们通常可以将其作为一种隐私保护技术,但在数据保护方面的情况则更为复杂。主要是因为它可能引发的一些问题,例如偏差或准确性不足。现在,虽然这乍一看可能是件坏事,数据保护再次让另一种有前景的方法受阻,但这可能仅仅是我们对好老 GDPR 做出了不必要的悲观看法。也就是说,这些问题同样困扰着人工智能开发者,他们经常提到数据分布偏差、不完整和不准确的数据、噪声缺乏、过度平滑或递归等问题,都是在使用合成数据时降低模型性能的因素。无论这个模型最终是否影响到人类,没有人愿意使用一个不准确且有偏见的模型。最后,这些问题的严重性仍然在很大程度上取决于训练的模型类型及其预定用途。不过,这确实意味着,律师和人工智能开发者实际上关心的许多问题是一样的,只是乍一看不太明显。这是件好事。它意味着我们只需要相互交流,共同解决问题。
最后思考
那么,合成数据是朋友还是敌人?它既不是也同时是。事实上,这里是一个经典的双刃剑例子。合成数据在解决一些现有问题的同时,也带来了新的问题。这不仅仅适用于隐私,它同样适用于性能目标,例如可扩展性和数据增强可能与偏见放大或泛化问题相对立。这并不是让我们放弃或重复那些过于泛化或只聚焦于某个微小点的利弊文章与分析的理由。这种做法也使得任何阅读某篇文章的人都无法看到树后面的森林。
在训练机器学习模型的过程中,合成数据的效用和适用性将始终取决于具体情况。它将取决于我们需要用于训练模型的数据类型(个人数据、受版权保护的数据、高度敏感的数据),所需数据的数量、数据的可用性以及模型的预期用途(因为不准确或偏见放大在评估信用 worthiness 的模型和优化供应链的模型中的权重是不同的)。所以,也许我们可以通过回答这些问题来开始任何特定情境,然后在更合适的环境中考虑各种现有的权衡。
关键要点:
· 合成数据绝不应为伪匿名数据。
· 合成数据应始终保持匿名。
· 合成数据不仅仅与隐私有关。
· 虽然合成数据始终有助于保护隐私,但它也带来了其他数据保护问题。
· 隐私和数据保护并非相同的概念。
· 一些数据保护问题也恰好是性能问题。这是好事,因为这意味着我们所有人(至少有时)都在尝试解决同一个问题。
· 与合成数据相关的所有权衡都非常依赖于具体情境,并应在相关背景下进行讨论。
Max Gruber / 更好的人工智能图像 / Ceci n’est pas une banane / CC-BY 4.0
[1](#_ftnref1) 探索合成数据:优势与应用场景,Intuit Mailchimp,mailchimp.com/resources/what-is-synthetic-data/
[2](#_ftnref2) John Anthony R, 关于人工智能——合成数据有一个不为人知的小秘密,www.linkedin.com/pulse/when-comes-aisynthetic-data-has-dirty-little-secret-radosta/
[3](#_ftnref3) Michael Yurushkin, 合成数据如何解决人工智能偏见问题?,Brouton Lab 博客,broutonlab.com/blog/ai-bias-solved-with-synthetic-data-generation/
[4](#_ftnref4) Giuffrè, M., Shung, D.L. 在医疗保健中利用合成数据的力量:创新、应用和隐私。npj 数字医学,6,186(2023)。doi.org/10.1038/s41746-023-00927-3
[5](#_ftnref5) GDPR
[6] AEDP,关于匿名化的 10 个误解,edps.europa.eu/system/files/2021-04/21-04-27_aepd-edps_anonymisation_en_5.pdf
[7] GDPR 第 26 条说明
[8] AEDP,关于匿名化的 10 个误解,edps.europa.eu/system/files/2021-04/21-04-27_aepd-edps_anonymisation_en_5.pdf
[9] Robert Riemann,合成数据,欧洲数据保护监督员。
[10] Alex Hern,《“匿名化”数据永远不可能完全匿名,研究表明》,《卫报》,2019 年 7 月 23 日,www.theguardian.com/technology/2019/jul/23/anonymised-data-never-be-anonymous-enough-study-finds
;Emily M Weitzenboeck, Pierre Lison, Malgorzata Cyndecka, Malcolm Langford, GDPR 与非结构化数据:匿名化是否可能?,《国际数据隐私法》,第 12 卷,第 3 期,2022 年 8 月,184–206 页,doi.org/10.1093/idpl/ipac008
[11] H. Deng, 探索用于人工智能和自主系统的合成数据:入门指南,
瑞士日内瓦:UNIDIR,2023,unidir.org/wp-content/uploads/2023/11/UNIDIR_Exploring_Synthetic_Data_for_Artificial_Intelligence_and_Autonomous_Systems_A_Primer.pdf
。
系统设计:布隆过滤器
智能地将哈希表转化为概率数据结构,牺牲精度以换取更大的内存空间
·发表于Towards Data Science ·阅读时间 6 分钟·2024 年 3 月 24 日
--
介绍
哈希表是最广为人知和使用的数据结构之一。通过合理选择哈希函数,哈希表能够在常数时间内为插入、查找和删除操作提供最佳性能。
哈希表的主要缺点是潜在的冲突。为了解决这个问题,标准方法之一是增加哈希表的大小。虽然这种方法在大多数情况下效果良好,但有时我们仍然会受到使用大量内存空间的限制。
需要回顾的是,哈希表总是能够为任何查询提供正确的响应。虽然它可能会发生冲突并且有时会变慢,但它始终****保证 100%正确的响应。事实证明,在某些系统中,我们并不总是需要收到正确的查询信息。对准确性的这种小幅下降可以用来专注于改善系统的其他方面。
在本文中,我们将探索一种创新的数据结构——布隆过滤器。简而言之,它是标准哈希表的一个修改版本,通过牺牲少量的准确性来换取内存空间的优化。
系统设计:一致性哈希
解锁分布式数据库中高效数据分区的力量,如 Cassandra 和 Dynamo DB。
·发布于Towards Data Science ·7 分钟阅读·2024 年 3 月 13 日
--
介绍
我们生活在一个每天大量生成数据的世界。在大型企业中,几乎不可能将所有数据存储在一台服务器上。这就是为什么我们需要水平扩展,即将每部分数据存储在不同的服务器上。
与垂直扩展(vertical scaling)不同,垂直扩展只需将所有数据存储在单一位置,而在水平扩展(horizontal scaling)中,必须以一种能够在不同服务器上快速访问数据的方式组织存储。通过理解简单系统实现的性能缺陷,我们将设计一个具有弹性的系统,以缓解这些问题。
在系统设计中,我们将使用的原则称为一致性哈希(consistent hashing)。
问题
假设我们有 n 个数据对象需要存储在 k 台不同的服务器上。服务器的配置可能会随着时间的推移发生变化:
-
任何服务器都可以关闭;
-
可以向系统中添加一台新服务器。
系统设计:负载均衡器
为微服务应用中的工作负载分配设计最佳策略
·发布于 Towards Data Science ·阅读时长 8 分钟·2024 年 6 月 28 日
--
介绍
大型分布式应用程序处理的请求量超过每秒数千个。在某些时候,处理单台机器上的请求变得不再可能。这就是为什么软件工程师关注水平扩展,在这种模式下,整个系统在多个服务器上有条不紊地组织。在这种配置中,每台服务器仅处理所有请求的一部分,基于其容量、性能和其他几个因素。
服务器之间的请求可以以不同的方式分配。在本文中,我们将研究最流行的策略。顺便说一下,无法概述出最佳策略:每种策略都有其独特的属性,应该根据系统配置进行选择。
负载均衡器的使用
负载均衡器可以出现在不同的应用层次中。例如,大多数 Web 应用程序由前端、后端和数据库层组成。因此,可以在不同的应用部分使用多个负载均衡器来优化请求路由:
-
在用户(客户端)和前端服务器之间;
-
在前端和后端之间……
系统设计:四叉树与 GeoHash
现实世界应用中优化搜索的高效地理数据管理
·发布于 Towards Data Science ·阅读时间:6 分钟·2024 年 5 月 8 日
--
引言
Google Maps 和 Uber 只是使用地理数据的最流行应用程序中的一部分示例。存储全球数百万个地点的信息要求它们高效地存储和处理地理位置,包括距离计算和最近邻搜索。
所有现代地理应用程序都使用以经度和纬度表示的二维位置。虽然将地理数据存储为坐标对可能显得很天真,但这种方法中存在一些陷阱。
在本文中,我们将讨论天真方法背后的潜在问题,并介绍另一种用于加速大规模系统中数据处理的现代格式。
注意:在本文中,我们将把世界表示为一个大的平面二维矩形,而不是三维椭球体。经度和纬度将分别用 X 和 Y 坐标表示。这种简化有助于使解释过程更为简便,同时不会遗漏主要细节。
问题
假设有一个数据库存储所有应用对象的二维坐标。用户登录后……
t 检验:从应用到理论
为了弥合数学计算与双样本 t 检验程序实现之间的差距,本文提供了一个使用实际案例的逐步指南。它包括统计工具的概述、使用这些工具的动机,以及对结果及其解释的说明。
· 发表在 Towards Data Science · 阅读时间:6 分钟 · 2024 年 7 月 12 日
--
图片来源:Steve Mushero 在 Unsplash
你是否曾经陷入过一个循环,不断复习统计工具的概念,记住它们并重新回顾,但这些概念仍然难以牢记?你知道如何使用公式,但总感觉这些只是对概念的表面理解。在我成为德州大学奥斯汀分校物理实验课程 105M 的助教并应用与我们解决问题相关的统计工具时,我也曾经历过同样的困境。正是在那时,我最终理解了学生 t 检验的理论和应用,现在我真的掌握了它。
让我们从一个问题开始。
颜色是否影响两个相似球体在斜坡上的滑动时间?
直观地,问题的答案可能是“没有”!但让我们通过使用常见的统计测试来验证这个假设。在假设检验的术语中:
-
原假设 表示颜色不会影响滚动时间(没有效应)
-
备择假设 表示颜色确实会影响滚动时间(存在效应)。
实验示意图 — 两个颜色不同的相似球体从斜坡上滚下
数据
我们首先通过多次试验(比如每个小球进行 10 次试验),分别测量两个不同颜色(例如一个黑色,一个红色)的小球沿斜坡滚动所需的时间。
不同试验中获得的滚动时间差异突出显示了进行多次试验的重要性,而不是仅进行一次试验,从而有助于提供更可靠的估计。
还需要注意的是,对于估计的滚动时间,可能有许多不同的值(总体),但我们只通过有限的试验捕获这些值的样本。
来自 10 次试验的实验数据
最佳估计
接下来,我们计算每个小球的预期值或最佳估计滚动时间。我们假设不同试验的时间记录形成了一个随机分布,并且预期值最好由该分布的均值或平均值表示。
两个分布的最佳估计/均值的数学计算(单位为秒)
最佳估计计算的程序实现
标准误差
如前所述,我们通过仅进行 10 次试验从所有可能的值(总体)中收集了有限的数据(样本)。请注意,计算出的最佳估计值来自样本分布。然而,为了更好地估计总体均值,我们计算样本分布的标准误差。标准误差帮助我们确定最佳估计值在总体中的可能范围。 它基于分布的方差,方差表示分布在均值周围的分散程度。
计算标准误差时,首先需要找到标准差(方差的平方根),然后将其除以数据点数量的平方根。
标准差和标准误差的数学计算(单位为秒)
标准误差计算的程序实现
我们观察到,两个小球的最佳估计值和标准误差是相似的(计算出的范围之间有重叠),这促使我们考虑到分布是相似的,因此颜色可能不会影响小球的滚动时间。然而,这些发现的统计学意义和可靠性如何? 从本质上讲,这些值是否提供了足够的证据,让我们对假设做出结论?
为了衡量我们结果的确定性,并以更易于交流的方式呈现证据,我们使用检验统计量。这些统计量帮助我们衡量获得这些结果的概率,提供一个确定性的度量。例如,如果已知总体标准差,则使用 z 统计量;如果只知道样本标准差(如在我们的实验中),则使用 t 统计量。
t 统计量
我们通过双样本 t 检验比较两个样本分布(组),该检验依赖于两个组的最佳估计和方差。根据两个组之间方差的相似性,我们决定使用合并方差(如学生 t 检验用于方差相等)或 Welch t 检验(用于方差不等)。
使用统计检验,如 F 检验或 Levene 检验,我们可以评估方差的相等性。
由于计算出的两个分布的标准差(方差的平方根)非常相似,我们继续使用方差相等的学生 t 检验。我们进行双尾检验,检查分布的不等式,而不是专门寻找较小或较大的值。
我们使用合并标准差以及从两个分布中获得的均值来计算 t 得分。
双样本学生 t 检验的数学计算
学生 t 统计量计算的程序实现
结果解读
正如我们观察到的,t 统计量基于两个样本均值的差异。在我们的案例中,t 统计量非常小(约-0.38),这表明两个分布的均值之间的差异也非常小。这表明两个球的记录相似,暗示出结论,即颜色对滚动时间没有显著影响。
然而,解释 t 统计量不仅仅是观察均值的微小差异,特别是因为我们只比较了两个样本(有限的试验),而不是整个总体。为了做出有根据的推断,我们需要确定临界值,然后将我们的 t 统计量与该临界值进行比较。
临界值是基于置信区间(例如,95%)和样本大小(自由度)确定的。95%置信区间(CI)意味着,如果实验重复多次,真实的均值差异将落在 95%计算出的区间内。
在我们的案例中,为了找到临界值或临界值范围(因为我们检查的是不等式),我们使用 t 分布表。对于95%置信区间的双尾检验,我们查看 0.05 显著性水平,该水平分为每个尾部 2.5%。根据我们的自由度(df = 18),临界值范围大约为-2.101 到+2.101。
双尾 t 分布,用于 95%的置信区间和 18 的自由度
我们的 t 统计量-0.38 落在 95%置信区间的临界范围内,这得出了两个关键推论。 首先,红球和黑球的滚动时间之间的观测差异非常小,表明颜色对滚动时间没有影响。其次,基于 95%的置信度,如果我们多次重复实验,红球和黑球滚动时间的真实均值差异将始终落在此范围内。
因此,我们的结果表明,两球的记录时间均值差异较小,在 95%的置信水平下是统计显著且可靠的,表明基于球的颜色,滚动时间没有实质性差异。
我很高兴记录下我的理解,希望能帮助那些像我一样在掌握这些统计工具时遇到困难的人。我期待看到其他人实现这些方法。如有未解答的问题,请随时与我联系或参考下面提到的文献。
除非另有说明,所有图片均由作者提供。
参考文献:
学生 t 检验是一种用于检验两组响应差异是否显著的统计检验…
统计量的标准误差(SE)(通常是参数的估计值)是其抽样分布的标准差…
计算沿指定轴的标准差。返回标准差,这是衡量数据分布的一种方法…
计算两个独立样本的均值 T 检验。这是一个检验零假设的测试,假设两个…
如何在 Power BI 中操作总计
在大多数情况下,总计会将可视化中的详细行汇总,比如在表格中。但是,如果总计应该显示不同的内容呢?我将在这里详细讨论这个挑战。
·发布于Towards Data Science ·8 分钟阅读·2024 年 7 月 16 日
--
图片来源:UX Indonesia Unsplash
介绍
首先,让我们看一下这个报告页面:
图 1 — 基础报告,包含求和和平均值度量(图由作者提供)
这个矩阵可视化没有什么特别之处。
现在,假设我想计算总计列和行中不同的销售平均值。
例如,一个加权平均数。
我不会深入探讨如何计算加权平均数,因为我已经写过一篇相关文章。我将在下面的参考文献部分添加一个链接。
为了做到这一点,我必须首先理解总计是如何计算的。然后,我可以开始操作它。
所以,让我们开始吧。
过滤上下文
即使你已经熟悉 DAX 中的过滤上下文,我还是建议你阅读这一部分,因为我将定义一些稍后使用的术语。
好的,让我们回到上面展示的矩阵可视化。
例如,“Contoso Paris Store”和“Economy”产品类别的度量值是如何计算的?
我选择任意单元格来解释它们的详细情况:
图 2 — 基础报告,突出显示的单元格(图由作者提供)
标记单元格的结果是在应用以下过滤器以定义过滤上下文后计算得出的。
-
状态:塞纳省(巴黎)
-
城市:巴黎
-
商店:Contoso 巴黎店
-
产品类别:经济型
-
年份:2024
-
月份:四月
-
大陆:欧洲
-
国家:法国
-
制造商:Proseware Inc.
所有这些过滤器都会影响这些特定单元格的结果。
当我们将矩阵“内部”过滤器与周围切片器的过滤器分开时,我们可以定义“内部”和“外部”过滤器:
图 3 — 带有内部和外部过滤器的基础报告(图由作者提供)
你可以看到内部过滤器用红色箭头标记,外部过滤器用蓝色箭头标记。
过滤器来自何处对度量值没有影响。然而,我们需要理解这两种过滤器源之间的区别,因为在大多数情况下,只有在计算总计时,我们才能对“内部”过滤器的变化做出反应。
那么,如何处理总计的过滤上下文呢?
在继续之前,考虑一下。
.
.
.
.
行和列总计是没有行和列过滤器时度量的计算结果。
总计结果来自所有“内部”过滤器的缺失。
所有外部过滤器仍然处于激活状态。
因此,我们可以检查是否应用了过滤器来操控总计。
概念验证
为了证明这一说法,我将度量更改为以下代码:
Avg Retail Sales = 1
这个度量的结果将始终为 1,无论过滤上下文如何,因为它没有使用过滤器。
实际上,这正是我们得到的结果:
图 4 — 操作后的度量结果(图由作者提供)
如果 Power BI 通过对可视化中的值求和来计算总计,结果将会非常不同。
由于度量的表达式中没有使用过滤上下文,因此始终返回 1,无论是否有销售。
记住这一点,当你稍后查看结果时。
可能的解决方案
要操控这个可视化中的总计结果,我必须检查地理层级和产品类别的过滤器。
我可以使用ISFILTERED()函数来实现这一点。
例如,当我想得到列总计为 1,行总计为 2 时:
Avg Retail Sales =
SWITCH(TRUE()
,**NOT** ISFILTERED('Geography' [StateProvinceName]), 1
,**NOT** ISFILTERED('Product'[ClassName]), 2
,AVERAGEX('Retail Sales', 'Retail Sales'[UnitPrice]*'Retail Sales'[SalesQuantity])
)
这个度量执行以下检查:
-
如果列“State”没有被过滤,位置是列总计,我希望它为 1。
-
如果产品类别没有被过滤,位置是行总计,我希望它为
-
如果两者都被过滤,我就在矩阵内部(而不是在总计中),并且希望得到平均零售销售额。
这是结果:
图 5 — 操控总计的度量结果。我移除了销售总和以简化可视化(图由作者提供)
如你所见,这是一个非常特定于可视化的解决方案。
由于我有一个切片器在过滤大陆和国家,我无法检查“NOT ISFILTERED(‘Geography’)”,因为当我从地理切片器中选择某个项目时,地理表已经被过滤。
但是,如果我知道在报告中通常使用哪些表或列,我可以设计一个更通用的解决方案。
例如,假设产品类别层级和商店通常作为行类别使用,而产品类别、商店类型和颜色则作为列类别使用。
例如,像这样:
图 6 — 修改后的零售销售报告,按产品类别层级和商店类型分类(图由作者提供)
在这种情况下,我可以设计两个度量值来检查这些列和层级上是否存在现有过滤器,并在 SWITCH()调用中使用它们。
这里是行过滤器:
Row filter Check =
ISFILTERED('Store')
||
ISFILTERED('Product'[ProductCategoryName])
||
ISFILTERED('Product'[ProductSubcategoryName])
||
ISFILTERED('Product'[ProductName])
对于列过滤器也是一样:
Column filter check =
ISFILTERED('Store'[StoreType])
||
ISFILTERED('Product'[ColorName])
||
ISFILTERED('Product'[ClassName])
如果我将这两个度量值添加到矩阵中,我可以看到结果:
图 7 — 过滤器检查度量值的结果(图由作者提供)
“列过滤器检查”度量值按预期工作。它始终返回 True,除了总计之外。
但是,“行过滤器检查”度量值并未按预期工作,因为即使是总计它也返回 True。
原因是列“商店类型”也来自商店表。
在检查过滤器时,我必须修改度量值,以去除该列上的任何过滤器:
Row filter Check =
CALCULATE(
ISFILTERED('Store')
,REMOVEFILTERS('Store'[StoreType])
)
||
ISFILTERED('Product'[ProductCategoryName])
||
ISFILTERED('Product'[ProductSubcategoryName])
||
ISFILTERED('Product'[ProductName])
现在,结果如预期所示:
图 8 — 过滤器检查度量值的正确结果(图由作者提供)
最后,我可以将度量值更改为使用这两个检查度量值来计算结果:
Avg Retail Sales =
SWITCH(TRUE()
,NOT [Row filter check], 1
,NOT [Column filter Check], 2
,AVERAGEX('Retail Sales', 'Retail Sales'[UnitPrice]*'Retail Sales'[SalesQuantity])
)
结果如预期所示:
图 9 — 使用目标度量值的结果(图由作者提供)
逻辑稍显复杂,因为我必须从列和行总计的角度考虑。这对于不习惯的人来说有些困惑。
下一步是插入返回处理结果的度量值,这个结果不同于简单的聚合。
我甚至可以选择以不同的方式操作总计,而不是行和列总计:
图 10 — 操作总计的 DAX 表达式和结果(图由作者提供)
由于 SWITCH()逐一评估检查,并返回第一个符合条件的表达式的给定结果,我必须注意每个变体检查的顺序。
最后一个小的调整
我可以为那些不喜欢在检查为假时执行某些操作的人提供一个小的调整。
上面的度量值检查表达式 = FALSE。
我可以将检查度量值更改为以下内容。然后,我可以避免在度量值中使用 NOT:
Column filter Check =
IF(
ISFILTERED('Store'[StoreType])
||
ISFILTERED('Product'[ColorName])
||
ISFILTERED('Product'[ClassName])
,FALSE()
,TRUE()
)
并且
Row filter Check =
IF(
CALCULATE(
ISFILTERED('Store')
,REMOVEFILTERS('Store'[StoreType])
)
||
ISFILTERED('Product'[ProductCategoryName])
||
ISFILTERED('Product'[ProductSubcategoryName])
||
ISFILTERED('Product'[ProductName])
,FALSE()
,TRUE()
)
现在我可以去掉度量值中的 NOT 反转:
Avg Retail Sales =
SWITCH(TRUE()
,[Row filter check] && [Column filter Check], 3
,[Row filter check], 1
,[Column filter Check], 2
,AVERAGEX('Retail Sales', 'Retail Sales'[UnitPrice]*'Retail Sales'[SalesQuantity])
)
这种方法比上述方法更直观。
结论
有时,我遇到必须操作总计结果的情况。
在这种情况下,了解 Power BI 的工作原理、过滤器上下文的应用以及总计的计算方式至关重要。
我尝试解释在查看矩阵时,过滤上下文是如何工作的。
但对于任何其他可视化也是一样的,它们可以使用类别来获取我们数据中的详细信息。
我写了一篇关于 DAX 中可用函数来探索过滤上下文的文章。
我强烈建议你也阅读它,因为这可能会为你提供更多的工具来正确检查过滤上下文:
在查看当前过滤上下文时,DAX 中有几个有用的函数。以下是一些例子:
towardsdatascience.com
然而,一个重要的经验是避免过于特定的度量值,这些度量值只能用于一个可视化。相反,跳出框框思考,构建更通用和可重用的代码。
这肯定会对你未来的工作有所帮助。
图片由 krakenimages 提供,来自 Unsplash
参考文献
这里是最初提到的关于计算加权平均值的文章链接:
平均值是一个简单的计算。但有时,背后还有更多可以探索的内容。让我们来看看这个被低估的话题。
towardsdatascience.com
和我之前的文章一样,我使用了 Contoso 示例数据集。你可以从微软的这里免费下载 ContosoRetailDW 数据集。
我将客户的请求转化为 ContosoRetailDW 数据集中的数据。
Contoso 数据可以在 MIT 许可证下自由使用,详情请见这里。
我扩大了数据集,以便让 DAX 引擎更加高效地工作。
在线销售表包含 7100 万行(而不是 1260 万行),零售销售表包含 1850 万行(而不是 340 万行)。
[## 每当 Salvatore Cagliari 发布时获取电子邮件通知。
每当 Salvatore Cagliari 发布时获取电子邮件通知。通过注册,你将创建一个 Medium 账户,如果你还没有的话……
medium.com](https://medium.com/@salvatorecagliari/subscribe?source=post_page-----b55e2d07207d--------------------------------)
尽管 Medium 有付费墙,我依然让我的文章对所有人开放。这让我能从每位读者那里赚取一些收入,但我关闭了付费墙,因此你可以免费阅读我的作品。
你可以通过以下方式支持我的工作,这些工作是我在空闲时间进行的:
buymeacoffee.com/salvatorecagliari
或者扫描此二维码:
任何支持都将不胜感激,并帮助我找到更多时间为你创作更多内容。
非常感谢。
应对复杂 LLM 决策制定:语言代理树搜索(LATS)与 GPT-4o 的结合
增强 LLM 决策制定:将语言代理树搜索与 GPT-4o 结合,实现卓越的问题解决能力
·发表于Towards Data Science ·9 分钟阅读·2024 年 8 月 26 日
--
作者图片:midjourney — 抽象拼图
大型语言模型(LLMs)在执行涉及复杂推理的自然语言任务中展现出了卓越的能力。因此,这些模型已经发展成能够规划、制定战略并解决复杂问题的代理。然而,当面临不确定性时,决策仍然存在挑战,尤其是在结果不是确定性的情况下,或者当在变化的环境中需要适应性决策时,尤其是在多步骤场景中,每一步都会影响到下一步。我们需要更先进的能力……
这是 GPT-4 的高级推理能力和语言代理树搜索(LATS)结合在一起,解决这些挑战的地方。LATS 结合了一种动态的基于树的搜索方法,增强了 GPT-4O 的推理能力。通过将蒙特卡洛树搜索(MCTS)与大语言模型(LLMs)结合,LATS 统一了推理、行动和规划,创造了一个更加深思熟虑且适应性强的问题解决框架。这一强大的组合提高了决策制定的能力,更加稳健地应对复杂任务,为语言模型作为自主代理的应用设立了新的标准。
“搜索”是生成式 AI 问题解决中的缺失环节吗?
作者图片:midjourney — 抽象拼图
计算问题解决可以广泛定义为“在组合问题空间中搜索”,通常表现为一棵树。深度优先搜索(DFS)和广度优先搜索(BFS)是探索这些解空间的基本方法。深度搜索的一个显著例子是 AlphaGo 的“第 37 手”,它展示了如何通过广泛探索涌现出创新的、超越人类的解决方案。
与遵循预定义路径的传统方法不同,LLMs 可以通过根据上下文预测潜在的结果、策略或行动,动态生成解空间中的新分支。这一能力使得 LLMs 不仅能够导航,还能扩展问题空间,使它们在问题结构尚不完全明确、不断演变或高度复杂的情况下具有非凡的能力。
基于推理时间的推理与元生成算法(MGA)
作者提供的图像:midjourney — 抽象拼图
在训练过程中扩展计算能力被广泛认可为提高模型性能的有效方法。在推理过程中扩展计算的好处仍然没有得到充分探索。 MGA 通过在推理过程中增强计算资源提供了一种新颖的方法……
与传统的标记级生成方法不同,元生成算法采用更高阶的控制结构,如规划、带有多个模型调用的循环、自我反思、任务分解和动态调节。这些机制使得模型能够端到端地执行任务,模仿通常被称为系统 2 思维的高级认知过程。
作者提供的图像:推理时间推理方法 — 摘要
因此,一种单向的元生成算法可能通过将搜索整合到生成过程中,增强 LLM 的推理能力。在推理过程中,MGA 动态地探索更广泛的解空间,使模型能够实时地推理潜在结果并调整策略。通过生成多个路径并评估其可行性,元生成算法使 LLMs 能够模拟出类似于传统搜索方法的更深层次、更复杂的推理。这种方法不仅扩展了模型生成新颖见解的能力,还改善了在信息不完整或不断变化的情况下的决策能力。
技术如思想树(ToT)和思想图(GoT)被用来高效地在组合解空间中导航。
-
ToT (*2**)通过将潜在结果结构化为树枝来实现分层决策,使得探索多个路径成为可能。
-
GoT (6***)** 映射了思想之间的复杂关系,使得模型能够动态调整并优化其推理路径。
-
CoT (*5**)提供逐步推理,链接顺序思维,增强生成的连贯性和深度。
为什么 MCTS 更好?
在思想树(ToT)方法中,传统方法如深度优先搜索(DFS)或广度优先搜索(BFS)可以遍历这棵树,但它们计算开销大,因为它们系统性地和穷举地探索每一条路径。
蒙特卡洛树搜索(MCTS)通过模拟不同的行动结果并根据这些模拟更新树结构,改进了这一点。它采用一种“选择”过程,通过平衡探索(尝试新路径)和利用(选择已知有效路径)的策略来选择决策节点。这一过程由一个称为上置信界(UCB)的公式指导。
UCB 公式有两个关键部分:
-
探索项: 这代表选择某个节点的潜在奖励,通过模拟进行计算。
-
利用项: 这个值随着你进入某条路径的深度而减少,这意味着如果某条路径被过度探索,算法可能会转向一个探索较少的路径,即使该路径最初看起来不那么有前景。
通过使用 UCB 选择节点,利用 LLM 模拟结果(奖励),并将奖励反向传播到树的上层,MCTS 有效地平衡了探索新策略和利用已知成功策略之间的关系。
UCB 公式的第二部分是‘利用项’,它随着你在特定路径中的探索深度增加而减少。这种减少可能导致选择算法切换到决策树中的另一条路径,即使那条路径的即时奖励较低,因为当该路径较少被探索时,利用项会保持较高。
使用 UCB 进行节点选择、通过 LLM 模拟进行奖励计算以及反向传播是 MCTS 的核心。
一种实现——金融决策……
LATS 操作 (1*) arxiv.org/pdf/2310.04406
为了演示,我们将使用 LATS 来解决在当今宏观经济环境中制定最佳投资策略的挑战性问题。我们将使用“国际货币基金组织世界经济展望报告”中的宏观经济状况作为上下文,将文档简要总结后输入 LLM。RAG 不使用。以下是 LATS 如何在解空间中进行搜索的示例……
迭代 1:
-
选择: 我们从根节点开始,由于这是第一次 LATS 迭代,我们将选择 LLM 生成的所有初始决策节点(A、B 和 C 节点)并模拟它们的结果。
-
模拟与反向传播: NextLLM 根据它所拥有的上下文“模拟”每个策略,并为每个“节点”分配以下“奖励”——投资回报。
-
策略 A:$5,000
-
策略 B:$7,000
-
策略 C:$4,000
- 扩展: 根据选择,策略 B 具有最高的 UCB1 值(因为所有节点处于相同深度),所以我们仅通过模拟其子节点来扩展 策略 B。
作者图示:由于模拟奖励值更高,B 节点被扩展
第 2 次迭代:
-
选择: 由于 B1 和 B2 策略没有被模拟,它们的 UCB 分数存在平局,因此两个节点都将被模拟。
-
模拟两个节点:
-
模拟 B1:LLM 预测 B1 的回报为 $8,500。
-
模拟 B2:LLM 预测 B2 的回报为 $7,500。
- 反向传播:
每次模拟后,模拟结果会被反向传播到树中,更新父节点的值。此步骤确保新信息的影响在整个树中得到反映。
更新策略 B 的值: 策略 B 现在需要反映 B1 和 B2 的结果。一种常见的方法是平均 B1 和 B2 的奖励,以更新 策略 B 的值。现在,策略 B 的更新值为 $8,000,基于其子节点的结果。
作者图示:策略 B 的奖励值在反向传播后被更新
- 重新计算 UCB 分数:
反向传播后,所有树中节点的 UCB 分数都会重新计算。此重新计算使用更新后的值(平均奖励)和访问次数,确保每个节点的 UCB1 分数准确反映其潜在奖励和已被探索的程度。
UCB(s) = (探索/奖励项) + (利用项)
再次注意,对于所有路径上持续被更深探索的节点,其利用项会减小。
5. 下一步选择与模拟:
B1 被选中进行进一步扩展(因为它具有更高的奖励),进入其子节点:
-
B1a:“投资于人工智能公司”
-
B1b:“投资于绿色技术”
作者图示:由于有更高的奖励,B1 节点进一步扩展
- 反向传播:
作者图示:子节点的奖励值被反向传播到父节点
B1 的奖励值更新为 (9200 + 6800) / 2 = 8000
B 的奖励更新为 (8000 + 7500) / 2 = 7750
7. UCB 计算:
在反向传播后,所有节点的 UCB 值都会重新计算。假设由于衰减的探索因子,B2 现在的 UCB 分数高于 B1a 和 B1b。如果 B1 已经被广泛探索,导致其子节点的探索项减少,那么这种情况就会发生。算法会转而探索 B2,因为 B2 因为未被探索的潜力变得更有吸引力,即其更高的利用价值。
作者提供的图片:当深入探索节点的路径时,节点的进一步利用价值会下降,这可能会触发分支切换——即通过新的决策节点进一步探索的新路径
该示例展示了 MCTS 如何基于新信息动态调整其搜索路径,确保算法在进行过程中仍然高效,并集中关注最有前景的策略。
使用 Azure OpenAI GPT-4o 的实现
接下来我们将使用 GPT-4o 构建一个“财务顾问”,实现 LATS。(代码请参阅 GitHub 仓库这里。)
(为了进行准确分析,我正在使用 IMF 世界经济展望报告 (2023 年 7 月 24 日发布)作为我的 LLM 上下文进行模拟,即用于生成子节点并为决策节点分配奖励…)
这是代码运行的方式…
LATS 迭代 MCTS 遍历决策树,创建新节点并探索树
该代码利用graphviz
库可视化表示在执行投资策略模拟过程中生成的决策树。由于决策树过于宽广,无法容纳在一张图片中,因此我已经添加了关于树形图样式的片段。您可以在 GitHub 仓库这里找到一个决策树示例…
作者提供的图片:MCTS 代码运行示例,用于在当前宏观经济环境下寻找最佳投资策略
作者提供的图片:来自生成的决策树的屏幕截图
以下是 LATS 推断出的最佳策略…
Optimal Strategy Summary: The optimal investment strategy is structured around several key steps influenced by the IMF report. Here's a concise summary of each step and its significance:
1\. **Diversification Across Geographies and Sectors:**
- **Geographic Diversification:** This involves spreading investments across regions to mitigate risk and tap into different growth potentials. Advanced economies like the U.S. remain essential due to their robust consumer spending and resilient labor market, but the portfolio should include cautious weighting to manage risks. Simultaneously, emerging markets in Asia, such as India and Vietnam, are highlighted for their higher growth potential, providing opportunities for higher returns.
- **Sector Diversification:** Incorporating investments in sectors like green energy and sustainability reflects the growing global emphasis on renewable energy and environmentally friendly technologies. This also aligns with regulatory changes and consumer preferences, creating future growth opportunities.
2\. **Green Energy and Sustainability:**
- Investing in green energy demonstrates foresight into the global shift toward reducing carbon footprints and reliance on fossil fuels. This is significant due to increased governmental supports, such as subsidies and policy incentives, which are likely to propel growth within this sector.
3\. **Fintech and E-Commerce:**
- Allocating capital towards fintech and e-commerce companies capitalizes on the digital transformation accelerated by the global shift towards digital platforms. This sector is expected to grow due to increased adoption of online services and digital payment systems, thus presenting promising investment opportunities.
结论:
通过整合 LATS,我们利用大型语言模型(LLMs)的推理能力,动态模拟和评估潜在的策略。这种结合不仅能构建出代表决策逻辑进展的决策树,还能根据 LLM 通过模拟和反思提供的变化的背景和见解进行自适应。
(除非另有注明,所有图片均来自作者)
参考文献:
1 语言代理树搜索:统一推理、行动和规划在语言模型中的应用 由 Zhou 等人撰写
2 思维树:利用大型语言模型进行深思熟虑的问题解决 由 Yao 等人撰写
3 新兴 AI 代理架构在推理、规划和工具调用中的应用:一项调查 由 Tula Masterman、Mason Sawtell、Sandi Besen 和 Alex Chao 撰写
4 从解码到元生成:大型语言模型的推理时间算法 由 Sean Welleck、Amanda Bertsch、Matthew Finlayson、Hailey Schoelkopf、Alex Xie、Graham Neubig、Ilia Kulikov 和 Zaid Harchaoui 撰写。
5 连锁思维提示引发大语言模型的推理,作者:Jason Wei, Xuezhi Wang, Dale Schuurmans, Maarten Bosma, Brian Ichter, Fei Xia, Ed H. Chi, Quoc V. Le 和 Denny Zhou。
[7] 思维图谱:使用大语言模型解决复杂问题,作者:Maciej Besta, Nils Blach, Ales Kubicek, Robert Gerstenberger, Michał Podstawski, Lukas Gianinazzi, Joanna Gajda, Tomasz Lehmann, Hubert Niewiadomski, Piotr Nyczyk 和 Torsten Hoefler。
[8] 从解码到元生成:大语言模型的推理时算法,作者:Sean Welleck, Amanda Bertsch, Matthew Finlayson, Hailey Schoelkopf, Alex Xie, Graham Neubig, Ilia Kulikov 和 Zaid Harchaoui。
使用 bart-large-mnli 标记登山事故报告
使用问题类型标记登山事故报告。
·发表于 Towards Data Science ·21 分钟阅读·2024 年 2 月 5 日
--
几周前,我发现了喜马拉雅数据库,并决定基于这个数据集创建一些“异想天开”的可视化图表。在之前的两篇文章中,我分别为珠穆朗玛峰探险创建了一个简单的 elevation plot,并展示了 5 座喜马拉雅山峰的死亡人数的对比图。这一次,我想探索探险事故报告。
我将使用的数据集是一个小型 CSV 文件,包含了喜马拉雅探险的信息(大约 11,300 行),每一条记录/行代表一次喜马拉雅探险。以下是 5 条安纳普尔纳二号山的探险样本记录:
expid peakid year season host route1 route2 route3 route4 nation leaders sponsor success1 success2 success3 success4 ascent1 ascent2 ascent3 ascent4 claimed disputed countries approach bcdate smtdate smttime smtdays totdays termdate termreason termnote highpoint traverse ski parapente camps rope totmembers smtmembers mdeaths tothired smthired hdeaths nohired o2used o2none o2climb o2descent o2sleep o2medical o2taken o2unkwn…
看一看引擎盖下的情况
使用单义性理解大型语言模型所学到的概念
·发表于Towards Data Science ·阅读时间 10 分钟·2024 年 6 月 13 日
--
就像大脑一样,理解 LLM 内部到底发生了什么是相当困难的。照片由Robina Weermeijer提供,来源:Unsplash
随着大型语言模型(LLMs)使用的增加,我们对它们的推理和行为的理解需求也在增加。在本文中,我想向你介绍一种方法,它揭示了 LLM 内部表示的概念。在这种方法中,提取了一种表示方法,使人们能够理解模型的激活,并通过离散的概念来解释给定输入。这被称为单义性,意味着这些概念只有一个(mono)含义(semantic)。
在这篇文章中,我将首先描述单义性(Monosemanticity)背后的主要思想。为此,我将解释稀疏自编码器,这是该方法中的核心机制,并展示它们是如何以可解释的方式构建大型语言模型(LLM)的激活的。接着,我将回顾一些单义性方法的作者提出的示范,以解释他们方法的洞见,这些洞见紧密跟随了他们的原始出版物。
稀疏自编码器
就像沙漏一样,自编码器有一个数据必须通过的瓶颈。照片由Alexandar Todov拍摄,来自Unsplash
我们必须首先了解稀疏自编码器。首先,自编码器是一个神经网络,它的训练目标是重现给定的输入,也就是说,它应该产生与输入完全相同的向量。现在你可能会问,这样做的意义何在?关键的细节在于,自编码器有一些中间层,它们的大小比输入和输出都要小。通过这些层传递信息必然会导致信息丢失,因此模型不能仅仅通过记住元素来完全重现它。它必须通过一个瓶颈传递信息,因此需要提出一种密集的输入表示,它仍然能够尽可能地重现输入。我们将模型的前半部分称为编码器(从输入到瓶颈),后半部分称为解码器(从瓶颈到输出)。在训练完模型之后,你可以丢弃解码器。编码器现在将给定的输入转换为一种表示,保留了重要信息,但具有与输入不同的结构,并且可能去除了不需要的数据部分。
为了使自编码器变得稀疏,它的目标得到了扩展。除了尽可能地重建输入外,模型还被鼓励尽可能少地激活神经元。与其让所有神经元都稍微激活一下,不如集中精力激活其中的少数神经元,并且激活值要高。这也使得在模型中可以有更多的神经元,从而让瓶颈在模型架构中消失。然而,激活过多神经元会受到惩罚,这仍然保持了尽可能压缩数据的思想。被激活的神经元将被期望表示描述数据的有意义的关键概念。我们从现在开始称它们为特征。
在原始的《单义性》出版物中,这种稀疏自编码器是在Claude 3 Sonnet模型的中间层上训练的(Claude 3 是由Anthropic发布的一个大型语言模型,可以说与 OpenAI 的 GPT 模型处于同一水平)。也就是说,你可以将一些标记(即文本片段)传递给 Claude 3 Sonnet 模型的前半部分,并将该激活传递给稀疏自编码器。然后,你将得到一个表示输入的特征激活。然而,到目前为止,我们并不真正知道这些特征是什么意思。为了找出答案,我们假设将以下文本输入模型:
-
猫在追逐狗。
-
我的猫整天躺在沙发上。
-
我没有猫。
如果有一个特征在所有三句话中都被激活,你可以猜测这个特征代表的是猫的概念。然而,也可能存在一些只在某一句话中激活而不在其他句子中激活的特征。对于第一句,你可以预期狗的特征会被激活;而为了表达第三句的意思,你可以预期会有一个代表某种否定或“没有某物”的特征。
不同的特征
特征可以描述非常不同的事物,从苹果和香蕉到可以食用和味道甜美的概念。照片由Jonas Kakaroto提供,来源于Unsplash
从前面提到的例子中,我们看到特征可以描述非常不同的事物。有些特征代表具体的物体或实体(例如猫、埃菲尔铁塔或班尼迪克特·康伯巴奇),但也有一些特征专门表示更抽象的概念,如悲伤、性别、革命、撒谎、能融化的东西或德语字母ß(是的,确实有一个只属于我们的额外字母)。由于模型在训练过程中也接触过编程代码,因此它还包含了许多与编程语言相关的特征,表示诸如代码错误或计算函数之类的上下文。你可以在这里探索 Claude 3 模型的特征。
如果模型能够使用多种语言进行表达,那么这些特征就是多语言的。这意味着,与悲伤相关的特征会在每种语言的相关句子中被激活。同样,如果模型能够处理不同的输入方式,那么这些特征也是多模态的。以班尼迪克特·康伯巴奇为例,该特征会在提到这个名字时被激活,也会在涉及到图片或口头提及班尼迪克特·康伯巴奇时被激活。
对行为的影响
特征可以影响行为,就像方向盘影响你行驶的方式一样。照片由Niklas Garnholz提供,来源于Unsplash
到目前为止,我们已经看到,当模型生成特定输出时,某些特征会被激活。然而,从模型的角度来看,因果关系的方向是相反的。如果金门大桥的特征被激活,它会促使模型生成与该特征概念相关的答案。接下来,我们将通过在模型推理过程中人为增加某个特征的激活来演示这一点。
模型的答案受某个特征高度激活的影响。图片来源于原始出版物。
左侧,我们看到正常设置下两个问题的答案,而右侧则显示了如果金门大桥(第一行)和大脑科学(第二行)特征的激活程度增加时,这些答案如何变化。很直观,激活这些特征会让模型生成包含金门大桥和大脑科学概念的文本。在通常情况下,这些特征由模型的输入和提示激活,但在我们看到的方法中,也可以通过更有意图和明确的方式激活某些特征。你可以想象,始终激活礼貌特征来引导模型的回答朝着期望的方向发展。如果没有特征的概念,你可能需要通过在提示中添加“在回答中始终保持礼貌”这样的指示来实现,但通过特征概念,这可以更明确地完成。另一方面,你也可以考虑显式地去激活某些特征,以避免模型告诉你如何制造原子弹或进行逃税。
深入探讨:特异性、敏感性和完整性
让我们更详细地观察这些特征。图片来自K8在Unsplash上。
现在我们已经理解了特征是如何提取的,我们可以参考一些作者的实验,展示模型实际学到的是哪些特征和概念。
首先,我们想知道特征的特异性,即它们与具体概念的契合度。我们可以问,代表本尼迪克特·康伯巴奇的特征是否仅仅对本尼迪克特·康伯巴奇起作用,而不会对其他演员产生作用?为了回答这个问题,作者们使用了一个 LLM 对文本进行评分,评估它们与给定概念的相关性。在以下示例中,评估了文本与“大脑科学”概念的相关性,评分范围从 0(完全无关)到 3(非常相关)。在下图中,我们看到这些评分以颜色表示(蓝色表示 0,红色表示 3),而在横轴上是激活水平。越往右走,特征的激活程度越高。
大脑科学特征的激活与输入的相关性得分。图片来源于原始出版物。
我们看到激活(x 轴)与相关性(颜色)之间有明确的相关性。激活越高,文本越常被认为与脑科学的主题高度相关。反过来,对于与脑科学主题关联较小或完全无关的文本,该特征仅在边缘上激活(如果有的话)。这意味着,该特征在脑科学主题上非常特定,并且对于相关主题(如心理学或医学)并不那么激活。
灵敏度
特异性的另一面是灵敏度。我们刚才看到的例子展示了一个特征仅在其主题上激活,而不是相关主题(至少不是那么多),这就是特异性。灵敏度现在提出的问题是:“它是否在每个提到该主题的地方都激活?”通常,灵敏性和特异性可以独立存在。一个特征可能只在脑科学的主题上激活(高特异性),但它可能在许多句子中都无法激活该主题(低灵敏度)。
作者在灵敏度的研究上花费的精力较少。然而,有一个易于理解的示范:金门大桥的特征会在许多不同语言中激活,尽管并未明确提到英文术语“Golden Gate Bridge”。更精细的分析在这里是相当困难的,因为并不总是清楚一个特征应该在细节上表示什么。例如,假设你有一个特征,你认为它表示本尼迪克特·康伯巴奇。现在你发现,它非常具体(仅对本尼迪克特·康伯巴奇有反应),但只对某些——而不是所有——图片做出反应。那么你如何知道,这个特征只是缺乏灵敏度,还是它代表的是一个更细化的子概念,例如BBC 系列中的福尔摩斯(由本尼迪克特·康伯巴奇饰演)?
完整性
除了特征对其概念的激活(特异性和灵敏度)之外,你可能会想知道模型是否涵盖了所有重要的概念。然而,决定应该包含哪些概念是相当困难的。你真的需要为本尼迪克特·康伯巴奇(Benedict Cumberbatch)创建一个特征吗?“悲伤”与“感到悲伤”是两个不同的特征吗?“行为不端”是一个独立的特征,还是可以通过“行为”和“否定”这两个特征的组合来表示?
为了快速了解特征的完整性,作者选择了一些类别的概念,这些概念的数量有限,例如周期表中的元素。在下图中,我们可以看到所有元素位于 x 轴上,并且可以看到是否已经为三种不同大小的自编码器模型(从 100 万到 3400 万个参数)找到对应的特征。
各种尺寸的自编码器中具有周期表元素特征的情况。图片来自原始出版物。
不足为奇的是,最大的自编码器具有比较小的自编码器更多的周期表元素特征。然而,它也并没有捕捉到所有的元素。不过,我们并不清楚这是否意味着模型没有清晰的概念,比如博尔赫里乌姆,还是它仅仅没有在自编码器中存活下来。
局限性
虽然我们看到了一些展示模型学习的概念特征的示范,但我们必须强调,这些实际上是定性的示范,而不是定量评估。所有的示例都很好地帮助我们了解模型实际上学到了什么,并展示了单义性方法的有用性。然而,需要一种正式的评估方式,系统地评估所有特征,才能真正支持从这些研究中获得的洞见。虽然这说起来容易,但实施起来很难,因为目前尚不清楚这种评估应该是什么样子。未来的研究需要找到方法,用定量和系统的数据来支撑这些示范。
摘要
单义性是一个有趣的路径,但我们还不知道它将把我们引向何方。照片由Ksenia Kudelkina提供,来自Unsplash
我们刚刚看到了一种方法,可以帮助我们深入了解大型语言模型在得出答案时可能利用的概念。一些示范展示了如何以相当直观的方式解释通过稀疏自编码器提取的特征。这预示着一种新的理解大型语言模型的方法。如果你知道模型有一个撒谎的概念特征,你就能预期它会这样做,拥有礼貌的概念(与没有礼貌相比)会大大影响它的回答。对于给定的输入,这些特征还可以用来理解模型的思维轨迹。当请求模型讲一个故事时,幸福结局特征的激活可能解释了它如何得出某个结局,而当模型做你的税务申报时,你可能想知道欺诈的概念是否被激活。
如我们所见,理解 LLM 的潜力相当巨大。不过,仍然需要更加正式和系统化的特征评估,以支持这种分析格式所带来的承诺。
来源
本文基于这篇出版物,其中将单义性方法应用于大型语言模型(LLM):
还有一项前期工作介绍了更基础模型中的核心思想:
关于已分析的 Claude 3 模型,请参见此处:
这些特性可以在这里探索:
喜欢这篇文章吗? 关注我 以便收到我的未来更新。
目标是选择具有最大影响力的变体
如何使用因果推断来改善关键的商业指标
·发表于 Towards Data Science ·7 分钟阅读·2024 年 8 月 30 日
--
Egor Kraev 和 Alexander Polyakov
图片由作者提供
假设你想给客户发送一封电子邮件,或者对你的客户界面进行更改,并且你有多个变体可以选择。你如何选择最佳选项?
最简单的方法是进行A/B/N 测试,将每个变体展示给你的客户的随机子样本,并选择得到最佳平均响应的变体。然而,这种方法假设所有客户都有相同的偏好,且隐含地认为客户之间的差异仅仅是需要平均的噪音。我们能做得比这更好吗?能否根据客户的可观察特征,为每个客户选择最适合的变体?
在评估实验结果时,真正的挑战在于根据可观察到的客户特征衡量每个变体的相对影响力。这并不像听起来那么简单。我们不仅仅关心具有特定特征的客户接收某个变体后的结果,还关心这个变体的影响力,即与另一个变体相比,结果的差异。
与结果本身不同,影响力是不可直接观察的。例如,我们无法同时向同一个客户发送和不发送相同的电子邮件。这带来了一个重大挑战。我们如何解决这个问题呢?
答案有两个层面:第一,我们如何最大化任务分配的影响?其次,一旦我们选择了一个分配策略,我们如何最好地衡量它与纯随机分配相比的表现?
比较不同任务分配的表现
第二个问题的答案比第一个更简单。做这件事的直觉方法是将你的客户群体分成两组,一组是完全随机分配变体,另一组则是根据最大化影响的最佳分配策略来分配——然后比较结果。然而,这样做是浪费的:每组的样本量只有总样本量的一半,因此你的平均结果会更加嘈杂;而且更有针对性的分配只能让样本中的一半客户享受到好处。
幸运的是,有一种更好的方法:首先,你应该让你的有针对性的分配也带有一定的随机性,只是偏向你认为在每种情况下最好的选项。这是合理的,因为你永远不能确定什么是每个特定客户的最佳选择;而且它允许你在收获已知成果的同时继续学习。
其次,当你收集到使用特定变体分配策略的实验结果时,你可以使用一种统计方法叫做ERUPT(或政策值)来获得任何其他分配策略,特别是随机分配变体的平均结果的无偏估计。听起来像魔法吗?不,这只是数学。查看ERUPT 基础中的笔记本,了解一个简单的示例。
寻找最佳任务分配
图片来源:作者
能够根据来自单一实验的数据比较不同任务的影响是非常棒的,但我们如何知道哪个任务分配策略是最好的呢?在这里,CausalTune再次发挥了作用。
我们如何解决上面提到的挑战——估算向同一客户展示不同变体所产生的结果差异——因为我们无法直接观察这种差异呢?顺便提一下,这种估算被称为提升建模,它是一种特定的因果建模。
直觉方法是将展示给每个客户的变体视为客户的另一个特征,并在结果特征和结果集上拟合你最喜欢的回归模型,例如XGBoost。然后,你可以查看如果我们仅改变“特征”中变体的值,拟合模型对于某个客户的预测会发生多大变化,并将此作为影响估计。这个方法被称为S-Learner。它简单直观,但根据我们的经验,通常表现非常糟糕。
你可能会想,我们怎么知道它表现得非常糟糕,既然我们无法直接观察到影响?一种方法是查看合成数据,在那里我们知道正确的答案。
但是,有没有一种方法可以在真实世界数据中评估影响力估计的质量呢,在这种情况下,真实值在任何特定案例中都是不可知的?事实证明是有的,我们认为我们的方法在这一领域具有原创贡献。假设有一个简单的情形,只有两种变体——对照组(没有处理)和实验组。对于一组给定的处理影响力估计(来自我们希望评估的特定模型),如果我们将该估计从处理样本的实际结果中减去,我们预计处理样本和未处理样本的(特征、结果)组合将具有完全相同的分布。毕竟,它们是从同一个总体中随机抽样的!现在,我们所需要做的就是量化这两个分布的相似性,这样我们就得到了一个影响力估计的评分。
现在你可以为不同的提升模型打分,你可以对它们的类型和超参数进行搜索(这正是 CausalTune 的用途),并选择最佳的影响力估计器。
CausalTune 目前支持两种评分方式,ERUPT 和能量距离。详情请参见原始的CausalTune 论文。
使用 CausalTune 优化影响力分配
如何在实践中利用这一点,以最大化你期望的结果,例如点击率?
你首先选择你的总体可达客户群体,并将其分为两部分。你从一个完全随机的变体分配实验开始,或者根据你之前的信念使用某些启发式方法。此时,至关重要的是,不论你的信念有多强,你总是要在每个分配中保留一些随机性 —— 你只能根据客户特征调整分配的概率,但绝不能让它们完全变为确定性的分配 —— 否则,你将无法从实验中学到更多!
一旦第一次实验的结果出来后,你可以首先像上述所述,使用 ERUPT 来估计你使用启发式分配方法相比完全随机分配所带来的平均结果的改进。但更重要的是,你现在可以在实验结果上拟合 CausalTune,从而根据客户特征生成实际的影响力估计!
然后,你可以利用这些估计来创建一个新的、更好的分配策略(可以通过为每个客户选择具有最高影响力估计的变体,或者更好的是,使用 汤普森抽样 在利用现有知识的同时持续学习),并将其应用于第二个实验,针对其余可达客户群体。
最后,你可以在第二次实验的结果上使用 ERUPT,以确定你新的策略相比随机策略以及之前的启发式策略的超越表现。
Wise 案例研究:优化点击率
我们在 Wise 的数据科学团队工作,拥有许多实际的因果推断和提升模型的应用案例。这里有一个 Wise 早期应用的故事,我们几乎就是这么做的。该电子邮件营销活动的目标是向现有的 Wise 客户推荐他们应该尝试的下一个产品。第一波电子邮件使用了一个简单的模型,我们通过查看现有客户使用每个产品的初次使用顺序,训练了一个梯度提升模型,预测给定前几个元素后,该序列中的最后一个元素是什么,不使用其他任何数据。
在随后的电子邮件营销活动中,我们使用该模型的预测来偏向分配,最终得到了1.90%的点击率——而根据ERUPT估算,相同实验结果下随机分配的点击率为1.74%。
然后,我们在这些数据上训练了CausalTune,并使用该模型制定了两种新的变体分配策略。第一个策略是“贪心”的,即始终选择模型预测最高影响的变体。第二个策略不仅使用了影响估算值,还使用了它们的标准差(也由模型提供),并采用了Thompson 采样来生成分配概率。
贪心分配的样本外ERUPT估算为2.18%,而Thompson 采样策略为2.22%,相比随机分配提高了25%!
一个令人惊讶的发现是,尽管Thompson 采样策略具有随机性,但其估计效果不逊色于贪心策略。这是一个好消息,因为像Thompson 采样这样的随机策略使我们能够继续从下一个实验结果中学习,同时最大化利用我们已有的知识。
因此,我们建议使用Thompson 采样从拟合的因果模型中创建策略,而不是使用贪心方法——更多内容将在下一篇文章中介绍。
我们目前正在准备该实验的第二波,以观察ERUPT预测的增益是否会在真实的点击率中显现。
结论
CausalTune 为您提供了一个独特的、创新的工具包,帮助您优化个体客户的目标定位,以最大化期望的结果,比如点击率。我们的AutoML因果估计器可以可靠地估算不同变量对客户行为的影响,而ERUPT估计器允许您将实际实验的平均结果与其他分配选项进行比较,提供了无需牺牲样本量的性能测量。
TARNet 和 Dragonnet:S-学习者与 T-学习者之间的因果推断
学习如何构建用于直接因果推断的神经网络
·发布于 Towards Data Science ·6 分钟阅读·2024 年 3 月 5 日
--
如今,构建机器学习模型已经变得相当简单,但往往,做出准确的预测并不足够。更进一步,我们希望能够做出关于干预措施的因果声明。知道一个客户将会离开我们的公司是好事,但知道该怎么做——例如,发送一张优惠券——要好得多。这需要更多的工作,我在另一篇文章中已经解释了基本概念。
使用你最喜欢的模型与元学习器相结合,做出有效的因果声明
towardsdatascience.com
在继续之前,我建议先阅读这篇文章。我向你展示了如何在你的特征形成足够的调整集时轻松得出因果声明,本文其余部分也将假设这一点。
该估计使用了所谓的元学习器。其中有 S-学习者和 T-学习者,每种都有各自的缺点。在本文中,我将向你展示另一种方法,可以视为这两种元学习器之间的权衡,从而获得更好的结果。
任务感知 RAG 策略:当句子相似性失效时的应对方法
改善超越语义相似性的检索
·发布于 Towards Data Science ·16 分钟阅读·2024 年 6 月 10 日
--
图片由 Joshua Golde 提供,来源于 Unsplash
向量数据库通过允许我们嵌入数据并使用相同的嵌入模型快速搜索,彻底改变了我们搜索和检索信息的方式,在推理时只有查询被嵌入。然而,尽管向量数据库具有令人印象深刻的能力,但它们也存在一个根本性缺陷:它们将查询和文档视为相同的。这可能导致次优的结果,尤其是在处理复杂任务如匹配时,因为查询和文档本质上是不同的。
任务感知 RAG(检索增强生成)的挑战在于其不仅需要基于语义相似性来检索文档,还需要结合额外的上下文指令。这为检索过程增加了一层复杂性,因为它必须考虑多个相关性维度。
这里有一些任务感知 RAG 问题的示例:
1. 将公司问题陈述与求职者匹配
-
查询:“寻找具有可扩展系统设计经验并且在优化大规模数据库方面有成功记录的候选人,适合应对我们当前的挑战:在现有基础设施上将数据检索速度提高 30%。”
-
上下文:该查询旨在直接将公司的具体技术挑战与具备相关技能和经验的潜在求职者联系起来。
2. 将伪域名与创业公司描述匹配
-
查询: “为一家专注于 AI 驱动的个性化学习平台的高中生创业公司匹配伪域名,强调互动式和自适应学习技术。”
-
上下文:旨在找到一个适合且引人注目的伪域名,反映出创业公司的创新和教育重点。伪域名是基于伪词的域名,伪词是听起来真实但并非真实的词语。
3. 投资者与创业公司配对
-
查询: “识别对早期生物技术创业公司感兴趣的投资者,重点关注个性化医学,并且有支持医疗行业种子轮投资的历史。”
-
上下文:该查询旨在匹配生物技术领域的创业公司,特别是那些从事个性化医学的公司,与那些不仅对生物技术感兴趣,而且曾在类似阶段和行业进行过投资的投资者。
4. 检索特定类型的文档
-
查询: “检索最近的研究论文和案例研究,讨论区块链技术在保障数字投票系统中的应用,重点关注在美国或欧洲选举中测试的解决方案。”
-
上下文:明确指出需要对区块链某一特定应用提供学术和实践洞察,突出地理相关性和最近的应用案例
挑战
让我们考虑一个情景,其中一家公司面临各种问题,我们希望将这些问题与最相关的职位候选人匹配,这些候选人拥有解决这些问题所需的技能和经验。以下是一些示例问题:
-
“高员工流动率正在促使对核心价值观和战略目标进行重新评估。”
2. “决策不透明的印象正在影响公司内部的信任水平。”
3. “远程培训课程缺乏参与度表明需要更具动态性的内容呈现方式。”
我们可以使用大语言模型生成每个问题的真实正样本和硬负样本候选。例如:
problem_candidates = {
"High employee turnover is prompting a reassessment of core values and strategic objectives.": {
"True Positive": "Initiated a company-wide cultural revitalization project that focuses on autonomy and purpose to enhance employee retention.",
"Hard Negative": "Skilled in rapid recruitment to quickly fill vacancies and manage turnover rates."
},
# … (more problem-candidate pairs)
}
尽管硬负样本表面上可能看起来相似,并且在嵌入空间中与查询更接近,但真实的正样本显然更适合解决特定问题。
解决方案:指令调优嵌入、重新排序和大语言模型(LLMs)
为了解决这个挑战,我们提出了一种多步骤的方法,结合了指令调优嵌入、重新排序和大语言模型(LLMs):
1. 指令调优嵌入
指令调优嵌入类似于双编码器,在该模型中,查询和文档的嵌入分别处理,然后对它们的嵌入进行比较。通过向每个嵌入提供额外的指令,我们可以将它们带入一个新的嵌入空间,在这个空间中,它们能够更有效地进行比较。
指令调优嵌入的关键优势在于,它们允许我们将特定的指令或上下文编码到嵌入本身。这在处理像职位描述与简历匹配这样的复杂任务时尤其有用,因为查询(职位描述)和文档(简历)的结构和内容是不同的。
通过在嵌入查询和文档之前添加特定任务的指令,我们理论上可以引导嵌入模型聚焦于相关方面并捕捉期望的语义关系。例如:
documents_with_instructions = [
"Represent an achievement of a job candidate achievement for retrieval: " + document
if document in true_positives
else document
for document in documents
]
该指令提示嵌入模型将文档表示为求职者成就,使其更适合根据给定的职位描述进行检索。
然而,RAG 系统在没有评估的情况下很难解读,因此让我们编写一些代码来检查三种不同方法的准确性:
1. 初级 Voyage AI 指令调优嵌入,没有额外指令。
2. Voyage AI 指令调优嵌入,包含额外的查询和文档上下文。
3. Voyage AI 非指令调优嵌入。
我们使用 Voyage AI 嵌入,因为它们目前是业内最顶尖的,并且在本文撰写时,稳居 MTEB 排行榜的顶部。我们还能够使用三种不同的策略,且向量维度相同,这将使得它们之间的比较更加容易。1024 维度也恰好比任何接近于表现得如此优秀的嵌入模型小得多。
MTEB 排行榜
理论上,我们应该看到指令调优的嵌入在此任务上表现优于非指令调优的嵌入,即便只是因为它们在排行榜上排名较高。为了验证,我们将首先嵌入我们的数据。
当我们这样做时,我们尝试在文档前添加字符串:“表示求职者最相关的经验以便检索:”,这为我们的嵌入提供了更多关于文档的上下文。
如果你想跟随操作,请查看这个colab 链接。
import voyageai
vo = voyageai.Client(api_key="VOYAGE_API_KEY")
problems = []
true_positives = []
hard_negatives = []
for problem, candidates in problem_candidates.items():
problems.append(problem)
true_positives.append(candidates["True Positive"])
hard_negatives.append(candidates["Hard Negative"])
documents = true_positives + hard_negatives
documents_with_instructions = ["Represent the most relevant experience of a job candidate for retrieval: " + document for document in documents]
batch_size = 50
resume_embeddings_naive = []
resume_embeddings_task_based = []
resume_embeddings_non_instruct = []
for i in range(0, len(documents), batch_size):
resume_embeddings_naive += vo.embed(
documents[i:i + batch_size], model="voyage-large-2-instruct", input_type='document'
).embeddings
for i in range(0, len(documents), batch_size):
resume_embeddings_task_based += vo.embed(
documents_with_instructions[i:i + batch_size], model="voyage-large-2-instruct", input_type=None
).embeddings
for i in range(0, len(documents), batch_size):
resume_embeddings_non_instruct += vo.embed(
documents[i:i + batch_size], model="voyage-2", input_type='document' # we are using a non-instruct model to see how well it works
).embeddings
然后,我们将向量插入到向量数据库中。对于这个演示,我们严格来说不需要一个数据库,但具备元数据筛选功能的向量数据库将使代码更简洁,并最终能够扩展此测试。我们将使用 KDB.AI,我是该平台的开发者倡导者。不过,任何具备元数据筛选功能的向量数据库都可以很好地工作。
要开始使用 KDB.AI,请访问kdb.ai以获取你的端点和 API 密钥。
然后,让我们实例化客户端并导入一些库。
!pip install kdbai_client
import os
from getpass import getpass
import kdbai_client as kdbai
import time
通过我们的端点和 API 密钥连接到我们的会话。
KDBAI_ENDPOINT = (
os.environ["KDBAI_ENDPOINT"]
if "KDBAI_ENDPOINT" in os.environ
else input("KDB.AI endpoint: ")
)
KDBAI_API_KEY = (
os.environ["KDBAI_API_KEY"]
if "KDBAI_API_KEY" in os.environ
else getpass("KDB.AI API key: ")
)
session = kdbai.Session(api_key=KDBAI_API_KEY, endpoint=KDBAI_ENDPOINT)
创建我们的表格:
# We use the default database
database = session.database("default")
# Define the schema
schema = [
{"name": "id", "type": "str"},
{"name": "embedding_type", "type": "str"},
{"name": "vectors", "type": "float64s"} # Use float64s as per the migration guide
]
# Define the index
indexes = [
{
"name": "embedding_index", # Name of the index
"type": "flat", # Index type
"params": {"dims": 1024, "metric": "CS"}, # Specify the dimensions and metric
"column": "vectors" # Apply the index to the 'vectors' column
}
]
# Create the table
table = database.create_table("data", schema=schema, indexes=indexes)
将候选成就插入到我们的索引中,并使用“embedding_type”元数据过滤器来区分我们的嵌入:
import pandas as pd
embeddings_df = pd.DataFrame(
{
"id": documents + documents + documents,
"embedding_type": ["naive"] * len(documents) + ["task"] * len(documents) + ["non_instruct"] * len(documents),
"vectors": resume_embeddings_naive + resume_embeddings_task_based + resume_embeddings_non_instruct,
}
)
table.insert(embeddings_df)
最后,评估上述三种方法:
import numpy as np
# Function to embed problems and calculate similarity
def get_embeddings_and_results(problems, true_positives, model_type, tag, input_prefix=None):
if input_prefix:
problems = [input_prefix + problem for problem in problems]
embeddings = vo.embed(problems, model=model_type, input_type="query" if input_prefix else None).embeddings
# Retrieve most similar items
results = []
most_similar_items = table.search(
vectors={"embedding_index": embeddings},
n=1,
filter=[("=", "embedding_type", tag)]
)
most_similar_items = np.array(most_similar_items)
for i, item in enumerate(most_similar_items):
most_similar = item[0][0] # the fist item
results.append((problems[i], most_similar == true_positives[i]))
return results
# Function to calculate and print results
def print_results(results, model_name):
true_positive_count = sum([result[1] for result in results])
percent_true_positives = true_positive_count / len(results) * 100
print(f"\n{model_name} Model Results:")
for problem, is_true_positive in results:
print(f"Problem: {problem}, True Positive Found: {is_true_positive}")
print("\nPercent of True Positives Found:", percent_true_positives, "%")
# Embedding, result computation, and tag for each model
models = [
("voyage-large-2-instruct", None, 'naive'),
("voyage-large-2-instruct", "Represent the problem to be solved used for suitable job candidate retrieval: ", 'task'),
("voyage-2", None, 'non_instruct'),
]
for model_type, prefix, tag in models:
results = get_embeddings_and_results(problems, true_positives, model_type, tag, input_prefix=prefix)
print_results(results, tag)
这是结果:
naive Model Results:
Problem: High employee turnover is prompting a reassessment of core values and strategic objectives., True Positive Found: True
Problem: Perceptions of opaque decision-making are affecting trust levels within the company., True Positive Found: True
...
Percent of True Positives Found: 27.906976744186046 %
task Model Results:
...
Percent of True Positives Found: 27.906976744186046 %
non_instruct Model Results:
...
Percent of True Positives Found: 39.53488372093023 %
指令模型在这个任务上的表现更差!
我们的数据集足够小,差异并不显著(不到 35 个高质量的示例)。
尽管如此,这仍然表明
a) 单独的指令模型不足以处理这个具有挑战性的任务。
b) 虽然指令模型可以在类似任务上取得良好的表现,但始终进行评估是很重要的,因为在这个案例中,我曾怀疑它们会表现得更好,但事实并非如此。
c) 有一些任务对于指令模型来说效果较差。
2. 重新排序
虽然指令/常规嵌入模型可以在某种程度上缩小我们的候选范围,但显然我们需要更强大的模型,能够更好地理解文档之间的关系。
在使用指令调优的嵌入检索到初步结果后,我们使用一个交叉编码器(重新排序器)进一步优化排名。重新排序器考虑了特定的上下文和指令,从而使查询和检索到的文档之间的比较更加准确。
重新排序至关重要,因为它使我们能够以更细致的方式评估检索到的文档的相关性。与仅仅依赖查询和文档嵌入之间的相似性的初始检索步骤不同,重新排序会考虑查询和文档的实际内容。
通过联合处理查询和每个检索到的文档,重新排序器能够捕捉细粒度的语义关系,并更准确地确定相关性评分。这在初始检索可能返回表面上相似但实际上与特定查询无关的文档的场景中特别重要。
这是我们如何使用 Cohere AI 重新排序器进行重新排序的一个示例(Voyage AI 也有一个很棒的重新排序器,但在我写这篇文章时,Cohere 的表现更好。此后,他们推出了一个新的重新排序器,根据他们的内部基准测试,它的表现与之前一样,甚至更好。)
首先,让我们定义我们的重新排序函数。我们也可以使用 Cohere 的 Python 客户端,但我选择使用 REST API,因为它似乎运行得更快。
import requests
import json
COHERE_API_KEY = 'COHERE_API_KEY'
def rerank_documents(query, documents, top_n=3):
# Prepare the headers
headers = {
'accept': 'application/json',
'content-type': 'application/json',
'Authorization': f'Bearer {COHERE_API_KEY}'
}
# Prepare the data payload
data = {
"model": "rerank-english-v3.0",
"query": query,
"top_n": top_n,
"documents": documents,
"return_documents": True
}
# URL for the Cohere rerank API
url = 'https://api.cohere.ai/v1/rerank'
# Send the POST request
response = requests.post(url, headers=headers, data=json.dumps(data))
# Check the response and return the JSON payload if successful
if response.status_code == 200:
return response.json() # Return the JSON response from the server
else:
# Raise an exception if the API call failed
response.raise_for_status()
现在,让我们评估我们的重新排序器。让我们还看看是否通过添加关于我们任务的额外上下文能提升性能。
import cohere
co = cohere.Client('COHERE_API_KEY')
def perform_reranking_evaluation(problem_candidates, use_prefix):
results = []
for problem, candidates in problem_candidates.items():
if use_prefix:
prefix = "Relevant experience of a job candidate we are considering to solve the problem: "
query = "Here is the problem we want to solve: " + problem
documents = [prefix + candidates["True Positive"]] + [prefix + candidate for candidate in candidates["Hard Negative"]]
else:
query = problem
documents = [candidates["True Positive"]]+ [candidate for candidate in candidates["Hard Negative"]]
reranking_response = rerank_documents(query, documents)
top_document = reranking_response['results'][0]['document']['text']
if use_prefix:
top_document = top_document.split(prefix)[1]
# Check if the top ranked document is the True Positive
is_correct = (top_document.strip() == candidates["True Positive"].strip())
results.append((problem, is_correct))
# print(f"Problem: {problem}, Use Prefix: {use_prefix}")
# print(f"Top Document is True Positive: {is_correct}\n")
# Evaluate overall accuracy
correct_answers = sum([result[1] for result in results])
accuracy = correct_answers / len(results) * 100
print(f"Overall Accuracy with{'out' if not use_prefix else ''} prefix: {accuracy:.2f}%")
# Perform reranking with and without prefixes
perform_reranking_evaluation(problem_candidates, use_prefix=True)
perform_reranking_evaluation(problem_candidates, use_prefix=False)
现在,这里是我们的结果:
Overall Accuracy with prefix: 48.84%
Overall Accuracy without prefixes: 44.19%
通过添加关于我们任务的额外上下文,可能会提升重新排序的性能。我们还看到我们的重新排序器表现优于所有嵌入模型,即使没有额外的上下文,因此它应该被毫无疑问地加入到流水线中。不过,我们的性能还不理想,准确率低于 50%(我们在不到 50%的查询中首先检索到最佳结果),一定有方法可以做得更好!
重新排序器的最佳部分是它们开箱即用,但我们可以使用我们的黄金数据集(包含困难负例的示例)来微调我们的重新排序器,使其更为准确。这可能大大提高我们的重新排序性能,但可能无法推广到不同类型的查询,并且每次输入变化时重新调整重新排序器可能会很令人沮丧。
3. LLM
在重新排序后仍然存在歧义的情况下,可以利用 LLM 来分析检索到的结果并提供额外的上下文或生成定向总结。
LLM,如 GPT-4,能够根据给定的上下文理解和生成类人文本。通过将检索到的文档和查询输入 LLM,我们可以获得更细致的洞察力并生成量身定制的回答。
例如,我们可以使用 LLM 总结与查询相关的检索文档中最相关的方面,突出显示求职候选人的关键资质或经验,甚至根据匹配结果生成个性化的反馈或推荐。
这很好,因为它可以在结果传递给用户后进行,但如果我们想重新排序几十个或几百个结果呢?我们的 LLM 上下文将会超出限制,获取输出的时间也会变得过长。这并不意味着你不应该使用 LLM 来评估结果并向用户传递额外的上下文,但这确实意味着我们需要一个更好的最终步骤重新排序选项。
假设我们有一个如下所示的管道:
简单管道
这个管道可以将数百万个可能的文档缩小到只有几十个。但是,最后的这几十个非常重要,我们可能只会传递三四个文档给 LLM!如果我们将一个求职候选人展示给用户,那么展示的第一个候选人比第五个更合适是非常重要的。
我们知道 LLM 是优秀的重新排序器,这有几个原因:
-
LLM 是了解列表的。 这意味着它们可以看到其他候选项并进行比较,这是可以利用的附加信息。假设你(一个人类)被要求对一个候选人进行 1 到 10 分的评分,展示所有其他候选人会有帮助吗?当然有!
-
LLM 真是太聪明了。 LLM 理解它们所接到的任务,并且基于此可以非常有效地判断某个候选人是否合适,无论是简单的语义相似度如何。
我们可以通过基于困惑度的分类器来利用第二个原因。困惑度是一个度量,用来估计 LLM 对某个输出的‘困惑’程度。换句话说,我们可以让 LLM 将我们的候选项分类为‘非常合适’或‘不太合适’。根据它将候选项归为‘非常合适’的确定性(即该分类的困惑度),我们可以有效地对候选项进行排序。
有各种各样的优化可以进行,但在一个好的 GPU 上(强烈推荐使用 GPU 进行这一部分),我们可以在大约同样的时间内重新排序 50 个候选项,而 cohere 则能重新排序 1000 个。然而,我们可以在多个 GPU 上并行化此计算,从而加速这一过程,并扩展到重新排序数千个候选项。
首先,让我们安装并导入lmppl,一个可以帮助我们评估某些 LLM 完成度困惑度的库。我们还将创建一个评分器,这是一个大型 T5 模型(任何更大的模型运行得太慢,更小的模型表现较差)。如果你能通过解码器模型实现类似的结果,请告诉我,因为那样会更容易带来额外的性能提升(解码器的性能提升和成本下降速度远快于编码器-解码器模型)。
!pip install lmppl
import lmppl
# Initialize the scorer for a encoder-decoder model, such as flan-t5\. Use small, large, or xl depending on your needs. (xl will run much slower unless you have a GPU and a lot of memory) I recommend large for most tasks.
scorer = lmppl.EncoderDecoderLM('google/flan-t5-large')
现在,让我们创建评估函数。这可以转化为任何重新排序任务的一般函数,或者你可以更改类别,看看是否能提高性能。这个例子似乎效果不错。我们缓存响应,以便重新运行相同的值时更快,但在 GPU 上这不是特别必要。
cache = {}
def evaluate_candidates(query, documents, personality, additional_command=""):
"""
Evaluate the relevance of documents to a given query using a specified scorer,
caching individual document scores to avoid redundant computations.
Args:
- query (str): The query indicating the type of document to evaluate.
- documents (list of str): List of document descriptions or profiles.
- personality (str): Personality descriptor or model configuration for the evaluation.
- additional_command (str, optional): Additional command to include in the evaluation prompt.
Returns:
- sorted_candidates_by_score (list of tuples): List of tuples containing the document description and its score, sorted by score in descending order.
"""
try:
uncached_docs = []
cached_scores = []
# Identify cached and uncached documents
for document in documents:
key = (query, document, personality, additional_command)
if key in cache:
cached_scores.append((document, cache[key]))
else:
uncached_docs.append(document)
# Process uncached documents
if uncached_docs:
input_prompts_good_fit = [
f"{personality} Here is a problem statement: '{query}'. Here is a job description we are determining if it is a very good fit for the problem: '{doc}'. Is this job description a very good fit? Expected response: 'a great fit.', 'almost a great fit', or 'not a great fit.' This document is: "
for doc in uncached_docs
]
print(input_prompts_good_fit)
# Mocked scorer interaction; replace with actual API call or logic
outputs_good_fit = ['a very good fit.'] * len(uncached_docs)
# Calculate perplexities for combined prompts
perplexities = scorer.get_perplexity(input_texts=input_prompts_good_fit, output_texts=outputs_good_fit)
# Store scores in cache and collect them for sorting
for doc, good_ppl in zip(uncached_docs, perplexities):
score = (good_ppl)
cache[(query, doc, personality, additional_command)] = score
cached_scores.append((doc, score))
# Combine cached and newly computed scores
sorted_candidates_by_score = sorted(cached_scores, key=lambda x: x[1], reverse=False)
print(f"Sorted candidates by score: {sorted_candidates_by_score}")
print(query, ": ", sorted_candidates_by_score[0])
return sorted_candidates_by_score
except Exception as e:
print(f"Error in evaluating candidates: {e}")
return None
现在,让我们进行重新排序和评估:
def perform_reranking_evaluation_neural(problem_candidates):
results = []
for problem, candidates in problem_candidates.items():
personality = "You are an extremely intelligent classifier (200IQ), that effectively classifies a candidate into 'a great fit', 'almost a great fit' or 'not a great fit' based on a query (and the inferred intent of the user behind it)."
additional_command = "Is this candidate a great fit based on this experience?"
reranking_response = evaluate_candidates(problem, [candidates["True Positive"]]+ [candidate for candidate in candidates["Hard Negative"]], personality)
top_document = reranking_response[0][0]
# Check if the top ranked document is the True Positive
is_correct = (top_document == candidates["True Positive"])
results.append((problem, is_correct))
print(f"Problem: {problem}:")
print(f"Top Document is True Positive: {is_correct}\n")
# Evaluate overall accuracy
correct_answers = sum([result[1] for result in results])
accuracy = correct_answers / len(results) * 100
print(f"Overall Accuracy Neural: {accuracy:.2f}%")
perform_reranking_evaluation_neural(problem_candidates)
以及我们的结果:
Overall Accuracy Neural: 72.09%
这比我们的重新排序器要好得多,而且不需要任何微调!不仅如此,它对任何任务都具有更大的灵活性,并且只需通过修改类别和提示工程就能更容易地获得性能提升。缺点是这种架构尚未优化,部署起来比较困难(我推荐使用modal进行多 GPU 的无服务器部署,或者在 VPS 上部署 GPU)。
有了这个神经任务感知的重新排序器,我们的工具箱就更强大了,我们可以创建一个更为稳健的重新排序管道:
强大的多阶段重新排序管道
结论
增强文档检索对于复杂的匹配任务需要一种多方面的方法,利用不同 AI 技术的优势:
1. 指令调优嵌入通过编码特定任务的指令,为模型提供基础,以指导其捕捉查询和文档中的相关方面。然而,评估是验证其性能的关键。
- 重新排序通过深度分析内容相关性来优化检索结果。它可以受益于任务背景的额外信息。
3. 基于 LLM 的分类器作为强大的最终步骤,使得对顶级候选项进行细致的重新排序,从而以优化的顺序呈现最相关的结果,满足最终用户需求。
通过精心设计的指令调优嵌入、重排器和大型语言模型(LLMs),我们可以构建强大的 AI 管道,擅长解决诸如将求职者与职位要求匹配等挑战。细致的提示工程、顶尖的模型以及 LLMs 固有的能力,使得我们能够构建更高效的任务感知型 RAG 管道——在这种情况下,能够在将人们与理想机会对接方面取得卓越的成果。采用这种多角度的方法使我们能够构建不仅仅是检索语义相似文档,而是能够真正理解需求并找到符合我们独特需求的智能文档的检索系统。
在LinkedIn上与我联系,获取更多 AI 工程技巧。
TE2Rules:解释“为什么我的模型这么说?”
将模型可解释性拓展到图像和文本之外
·发表于Towards Data Science ·阅读时间 8 分钟·2024 年 1 月 5 日
--
在快速发展的人工智能领域,最近的进展已将该领域推向了惊人的高度,使得模型能够模仿人类在处理图像和文本方面的能力。从以艺术家的技巧创作图像到生成引人入胜的标题、回答问题以及撰写完整的文章,人工智能已成为我们数字工具库中不可或缺的一部分。
然而,尽管这些非凡的成就已经取得,强大技术的全面采用仍然并非普及。人工智能模型的“黑箱”特性引发了重大关切,尤其是在那些对透明度要求极高的行业中。对于“为什么模型会这么说?”缺乏洞察引入了风险,例如毒性和不公平的偏见,特别是针对边缘群体。在医疗和金融等高风险领域,错误决策的后果代价高昂,因此可解释性变得至关重要。这意味着,模型不仅需要得出正确的决策,还同样重要的是能够解释这些决策背后的逻辑。
表格数据挑战
尽管能够处理、理解并生成更多图像或文本的模型已经成为许多人中的新热点,但许多高风险领域是基于如用户信息、用户点赞的帖子、购买历史、观看历史等数据表格来做决策的,
表格数据并不是一个新现象。它自互联网诞生以来就存在,例如用户的浏览历史、访问的页面、点击的互动、在线浏览的产品、在线购买的产品等。这些信息通常被广告商用来向你展示相关广告。
在金融、医疗、法律等高风险领域,许多关键的应用案例也严重依赖以表格形式组织的数据。以下是一些例子:
-
设想一个医院试图判断某个病人在接受某种治疗后康复的可能性。医院可能会使用包含患者数据的表格,数据可能包括年龄、以往的健康问题和治疗详情等因素。如果使用的模型过于复杂或是“黑箱模型”,医生可能很难信任或理解预测结果。
-
同样,在金融领域,银行会分析表格中的各种因素来决定一个人是否符合贷款资格以及应该提供什么样的利率。如果使用的模型过于复杂,那么就很难向客户解释为何作出某个决策,这可能导致客户对系统失去信任。
在现实世界中,许多关键的决策任务,如通过医学检测诊断疾病、根据财务报表批准贷款、根据风险概况优化投资、在社交媒体上识别虚假个人资料以及为定制广告定位合适的受众,都涉及从表格数据中做出决策。尽管深度神经网络(如卷积神经网络和像 GPT 这样的变换器模型)擅长处理图像、文本和语音等非结构化输入,但像 XGBoost 这样的树集成模型仍然是处理表格数据的无可匹敌的冠军。这可能在深度神经网络时代让人感到惊讶,但它确实是事实!用于表格数据的深度模型,如 TabTransformer、TabNet 等,表现与 XGBoost 模型相当,尽管它们使用了更多的参数。
解释 XGBoost 模型在表格数据中的应用
在这篇博客文章中,我们将解释 XGBoost 模型所做的二元分类决策。解释这类模型的一种直观方法是使用人类可以理解的规则。例如,考虑一个模型用来判断一个用户账户是否属于机器人。如果模型将一个用户标记为“机器人”,那么基于模型特征的可解释性说明可能是“与其他机器人的连接数量 ≥ 100 且每日 API 调用次数 ≥ 10k”。
TE2Rules是专门为此目的设计的算法。TE2Rules 代表树集成到规则(Tree Ensembles to Rules),其主要功能是通过生成从输入特征组合中衍生的规则,来解释任何面向二分类的树集成模型。该算法结合了从 XGBoost 模型中的多棵树提取的决策路径,使用的是未标记数据的子集。从 XGBoost 模型中提取规则所使用的数据不必与训练数据相同,也不需要任何真实标签。该算法利用这些数据揭示数据集中隐含的相关性。值得注意的是,TE2Rules 提取的规则在模型预测中具有很高的精度(默认值为 95%)。该算法系统地识别出 XGBoost 模型中的所有潜在规则,以解释正例实例,并随后将其浓缩为一组简明的规则,能够有效地覆盖数据中的大多数正例。这组简化的规则作为模型的全面全局解释器。此外,TE2Rules 保留了所有可能规则的较长规则集,可以通过使用简洁规则来解释特定实例。
演示:展示与讲解
TE2Rules 通过提供模型决策过程的洞见,展示了其在各个医学领域的有效性。以下是一些实例:
在本节中,我们展示了如何使用 TE2Rules 解释一个预测个人收入是否超过 50,000 美元的模型。该模型使用了来自 UCI 库的成人收入数据集进行训练。此博客中使用的 Jupyter 笔记本可以在此处获取:XGBoost-Model-Explanation-Demo。该数据集受 CC BY 4.0 许可协议保护,允许学术和商业用途。
步骤 1:训练 XGBoost 模型
我们在成人收入数据集上训练了一个具有 50 棵深度为 3 的树的 XGBoost 模型。我们注意到,训练完成后,XGBoost 模型在训练和测试数据集上的准确率约为 86%,AUC 为 0.91。这证明了模型的有效训练及其在测试数据上良好的泛化能力。
步骤 2:使用 TE2Rules 解释模型
使用 TE2Rules,我们从训练好的 XGBoost 模型中推导出规则。为了引导 TE2Rules 生成解释规则,我们使用了 10%的训练数据。值得注意的是,TE2Rules 总共识别出 1138 条规则,其中每条规则在满足时确保模型的正类预测。为了提高可用性,TE2Rules 将这些规则整合为一组简洁的 16 条规则,有效地用 50 棵树(每棵树的深度为 3)解释模型。
以下是完整解释该模型的 16 条规则:
每条规则的精度超过 95%。这些规则按在数据中的支持度降序排列。
第 3 步:使用 TE2Rules 解释特定输入
使用 TE2Rules,我们通过收集所有满足输入的 1138 条规则来解释一个特定的正类实例,从而解释该正类实例。在这些规则中,我们使用自定义逻辑对可能的解释进行排序,并选择最易理解的规则作为解释。
可解释性是高度主观的,因使用场景的不同而有所不同。在本示例中,我们使用规则中所使用特征的数量作为该规则可解释性的衡量标准。我们选择使用最少特征的规则作为最易理解的解释。以下是一个正类模型预测的示例输入,以及 TE2Rules 生成的关于该预测的解释:
我们注意到,在上面的示例中,模型使用 12 个特征将输入分类为正类。TE2Rules 通过一个只包含 3 个特征的规则来解释这一预测:“capital_gain > 543 且 education = 专业学校 且 occupation != 管理职位”。任何其他满足此规则的输入(来自相同的数据分布)都可以确保模型以超过 95%的概率将其分类为正类。
第 4 步:使用 TE2Rules 进行反事实解释
TE2Rules 提取出满足时能导致模型作出正类预测的规则。这些规则可以用来指出需要对负类分类输入进行的最小修改,以确保模型将其分类为正类。因此,TE2Rules 在生成反事实解释时非常有价值,反事实解释以规则的形式呈现,指定将负实例转化为正实例所需的条件。
与前一个场景类似,可接受的最小修改因上下文而异。在本示例中,某些特征(如年龄、关系状态和性别)被视为不可改变,而其他特征(如教育、职业和资本收益/损失)则被视为可改变的。对于任何负类实例,我们指出在这些可改变特征中单一的特征,展示所需的调整,以促使模型将该实例评分为正类。
我们注意到,在上述示例中,模型将输入分类为负面。TE2Rules 识别出 5 条不同的规则,只要满足其中任何一条规则,就足以使模型将预测从负面转为正面。接受不同教育背景,如职业学校、硕士或博士学位,或获得更多资本收益,都有助于个人赚取更多的钱。然而,令人惊讶的是,模型已经学会了即使资本损失很大,这个人仍然会变得富有!这可能是因为这样的变化会导致数据样本与训练数据的分布差异很大,使得模型在这种情况下的可靠性降低。
结论
可解释性对于建立对 AI 模型的信任至关重要,特别是在高风险场景中,模型的决策可能对人们的生活产生深远影响,比如在医疗、法律和金融领域。许多这些关键的应用场景都涉及基于表格格式的数据做出决策。XGBoost 通常是表格数据中最受欢迎的 AI 模型选择。
TE2Rules 作为一种多功能工具,用于解释 XGBoost 模型。TE2Rules 在医学领域已经获得了广泛关注,并且逐渐在其他领域也开始受到欢迎。在这篇博客中,我们展示了 TE2Rules 如何有效地解释“为什么我的模型会这么说?”
作为一个由作者共同创建的开源研究项目,TE2Rules 的源代码可以在[github.com/linkedin/TE2Rules
]找到。我们鼓励用户将 TE2Rules 集成到他们的可解释性项目中。如果你发现 TE2Rules 对你的项目有帮助,请通过给仓库加星来表达支持。如果在使用 TE2Rules 过程中遇到任何问题,请通过[github.com/linkedin/TE2Rules/issues
]联系我们,我们会尽力解决你的问题。
教授你的模型从自身学习
基于迭代和置信度的伪标签分类案例研究
·发表于 Towards Data Science ·6 分钟阅读·2024 年 9 月 16 日
--
在机器学习中,更多的数据通常会带来更好的结果。但标记数据可能非常昂贵且耗时。如果我们能够利用通常很容易获得的大量未标记数据呢?这就是伪标签方法的用武之地。
TL;DR:我在 MNIST 数据集上进行了案例研究,并通过应用迭代的、基于置信度的伪标签方法,将模型的准确率从 90%提高到了 95%。本文涵盖了伪标签的详细内容,以及我实验中的实用技巧和洞察。
它是如何工作的?
伪标签是半监督学习的一种方式。它弥合了监督学习(所有数据都有标签)和无监督学习(没有标签数据)之间的差距。
过程示意图,展示了在 MNIST 数据集上执行的步骤。来源:Yann LeCun、Corinna Cortes 和 Christopher J.C. Burges。根据 CC BY-SA 3.0 许可授权。
我遵循的具体步骤如下:
-
我们从少量标记数据开始,并在其上训练模型。
-
模型对未标记的数据进行预测。
-
我们选择模型最有信心的预测(例如,置信度超过 95%),并将其视为真实标签,希望这些预测足够可靠。
-
我们将这些“伪标签”数据添加到我们的训练集,并重新训练模型。
-
我们可以多次重复这个过程,让模型从不断增长的伪标签数据池中学习。
虽然这种方法可能会引入一些错误标签,但它的好处在于大幅增加了训练数据的数量。
回声室效应:伪标签方法能有效吗?
模型从自己预测中学习的想法可能会引起一些质疑。毕竟,我们不是在尝试从无到有,而是在依赖一个“回音室”,模型只是不断强化自己最初的偏见和错误,不是吗?
这个担忧是合理的。这可能让你想起传奇人物明茨豪森男爵,他曾声称自己通过自己的头发把自己和他的马从沼泽中拉了出来——这是物理上不可能的。类似地,如果一个模型完全依赖于自己可能存在缺陷的预测,它就有可能陷入自我强化的循环,就像那些被困在回音室中的人们,只听到自己信仰的回响。
那么,伪标签化真的可以有效避免陷入这个陷阱吗?
答案是肯定的。虽然明茨豪森男爵的故事显然是一个童话故事,但你可以想象一位铁匠随着时代的进步。他从基本的石器工具(最初的标注数据)开始,利用这些工具从原矿(无标签数据)中锻造出粗糙的铜器工具(伪标签)。这些铜器工具虽然仍然很粗糙,但使他能够进行之前不可行的任务,最终创造出青铜、铁等材料制成的工具。这个迭代过程至关重要:你不能仅用石锤锻造钢剑。
就像铁匠一样,在机器学习中,我们可以通过以下方式实现类似的进展:
-
严格的阈值:模型的样本外准确率受到正确训练标签比例的限制。如果 10%的标签是错误的,模型的准确率不会显著超过 90%。因此,尽可能减少错误标签的比例非常重要。
-
可衡量的反馈:不断在一个单独的测试集上评估模型的表现,充当现实检验,确保我们在取得实际进展,而不仅仅是在强化已有的错误。
-
人机协作:通过人工审查伪标签或人工标注低置信度数据的反馈,可以为调整方向提供宝贵的纠正。
当伪标签化正确执行时,它可以成为最大化利用小型标注数据集的强大工具,正如我们在接下来的案例研究中将看到的那样。
案例研究:MNIST 数据集
我在 MNIST 数据集上进行了实验,MNIST 是一个经典的 28×28 像素手写数字图像集合,广泛用于机器学习模型的基准测试。它包含 60,000 张训练图像和 10,000 张测试图像。目标是根据 28×28 像素的图像,预测所写的数字是什么。
我用 1,000 张带标签的图像训练了一个简单的 CNN 模型,剩下的 59,000 张图像没有标签。然后,我用训练好的模型预测无标签图像的标签。对于置信度高于某个阈值(例如 95%)的预测结果,将其添加到训练集中,并标上预测的标签。接着,模型在这个扩展的数据集上重新训练。这个过程反复进行,最多进行十次,或者直到没有更多的无标签数据为止。
此实验使用不同数量的最初标记图像和置信度阈值进行了重复。
结果
以下表格总结了我的实验结果,比较了伪标签与在完整标记数据集上训练的表现。
即使初始标记的数据集较小,伪标签仍然能产生显著的效果,对于 1,000 个初始标记样本,准确率提高了 4.87 个百分点。当仅使用 100 个初始样本时,这一效果更为显著。然而,手动标记超过 100 个样本会更明智。
有趣的是,使用 100 个初始训练样本的实验最终测试准确率超过了正确训练标签的比例。
相较于第一次迭代,按阈值(x 轴)和每次迭代(颜色)计算的准确率提升(y 轴)。更高的阈值和更多的迭代呈现出明显的改善趋势。图像由作者提供。
每次迭代中按阈值划分的正确训练标签的比例和总训练数据点数量。更高的阈值导致更稳健但更慢的标记。图像由作者提供。
每次迭代中按阈值划分的高置信度和低置信度预测的准确率。更高的阈值会导致更好的准确率,但随着时间推移,每个阈值选择的准确率都会下降。图像由作者提供。
与第一次迭代相比,按阈值对 100 个和 10,000 个最初标记的训练样本(分别位于左侧和右侧)计算的每次迭代的准确率提升。注意不同的刻度。图像由作者提供。
通过观察上述图表,可以明显看出,通常情况下,更高的阈值会导致更好的结果——只要至少有一些预测超过了阈值。在未来的实验中,可以尝试在每次迭代时变化阈值。
此外,准确率在后期迭代中仍然有所提高,这表明迭代的性质确实带来了真正的好处。
主要发现和经验教训
-
伪标签最适用于未标记数据丰富但标记成本高昂的情况。
-
监控测试准确率:在整个迭代过程中,重要的是要关注模型在单独的测试数据集上的表现。
-
手动标记仍然有帮助:如果你有资源,重点手动标记低置信度数据。然而,人类也不是完美的,高置信度数据的标记可以放心交给模型。
-
跟踪哪些标签是 AI 生成的。 如果以后有更多手动标记的数据可用,你可能会希望丢弃伪标签并重新进行此过程,从而提高伪标签的准确性。
-
解释结果时要小心:几年前我第一次做这个实验时,我专注于剩余未标记训练数据的准确率。随着迭代次数的增加,这个准确率下降了!然而,这很可能是因为剩余的数据更难预测——在之前的迭代中,模型对这些数据从未有过信心。我应该专注于测试集的准确率,实际上随着迭代次数的增加,它会提高。
链接
包含实验代码的仓库可以在这里找到。
相关论文:使用深度特征注释和基于置信度的采样的迭代伪标签方法
使用 Python 进行聊天数据分析的技巧
第一部分:沟通密度分析
·发表于Towards Data Science ·8 分钟阅读·2024 年 10 月 25 日
--
图片由Mikechie Esparagoza提供
图片来源于 Pexels.com
多年来,我们的沟通方式变得越来越数字化。无论是通过即时通讯应用发送简短的文本消息,还是通过电子邮件发送,数字信息交流已经深深嵌入我们的日常生活中。
这导致了数字化数据量的增加。
由于沟通至少涉及两个人,因此它可以揭示参与者及其彼此关系的许多见解。
本文将是一个系列的第一部分,在该系列中,我将展示您可以使用聊天记录做的有趣事情,以及您可以从中获得的个人见解。您可以在我的GitHub 个人资料中找到我为可视化创建的 Python 代码和任何 Tableau 文件。
目标
由于这是该系列的第一篇文章,我想从 WhatsApp 聊天的元数据进行高级分析开始。我将即将进行的分析命名为沟通密度分析,因为可视化的主要特征来自于发送消息的密度。
使用 Python 进行聊天数据分析的技术
第二部分:使用 BERTopic 进行主题提取
·发表于Towards Data Science ·阅读时长 10 分钟·2024 年 11 月 15 日
--
图片来源:Mikechie Esparagoza
图片来源:Pexels.com
在本系列的第一部分,我向大家介绍了我人工创建的朋友约翰,他非常友好地提供了他与五个最亲近的人的聊天记录。我们仅使用了元数据,比如谁在什么时间发送了消息,以可视化约翰何时遇到他的女朋友,何时与最好的朋友之一发生争执,以及他应该更常给哪些家人写信。如果你还没有阅读本系列的第一部分,可以在这里找到它。
我们还没有涉及的内容,但接下来会深入探讨的是对实际消息的分析。因此,我们将使用约翰和玛丽亚之间的聊天记录来识别他们讨论的主题。当然,我们不会一条一条地查看这些消息并进行分类——不,我们将使用 Python 库 BERTopic 来提取这些聊天围绕的主题。
什么是 BERTopic?
BERTopic 是一种由 Maarten Grootendorst 提出的主题建模技术,利用基于变换器的嵌入,特别是 BERT 嵌入,从大量文档中生成连贯且易于解释的主题。它旨在克服传统主题建模方法如 LDA(潜在狄利克雷分配)的局限性,因为传统方法通常难以处理简短的文本…
探索性数据分析技巧与统计图形的解释
发现统计可视化中的洞察和模式的实用方法
·发表于Towards Data Science ·49 分钟阅读·2024 年 11 月 12 日
--
datascience.stackexchange.com/questions/66356/machine-learning-methods-for-finding-outliers
(CC BY-SA)
概述
在这个项目中,我们将探索探索性数据分析技巧并深入研究统计图形的解释。你知道如何解释直方图或箱线图吗?
你能发现异常值或缺失值如何影响这些可视化效果吗?你能评估数据清洗需求以使这些解释更精确吗?
本项目将回答这些问题以及更多问题。项目设置在与会计相关的商业背景中,展示了在现实世界数据分析中常见的挑战。
本项目使用虚拟数据,模拟真实的会计场景,带领你了解分析和准备数据的关键步骤,以获得有意义的洞察。
技术与图形解释
你可以通过我的GitHub 仓库访问完整的项目代码和数据集,方便跟踪和学习……
医疗数据分析中的特征工程技术 — 第二部分。
医疗数据分析中的特征工程技术,重点讨论现实世界的挑战和实际解决方案。
·发表于Towards Data Science ·阅读时长 25 分钟·2024 年 11 月 20 日
--
概述
在本教程中,我们将继续项目特征工程技术:医疗数据分析的现实世界挑战 — 第一部分,深入探讨一系列新的特征工程技术。项目链接:GitHub
这一次,我们将利用领域知识使特征工程更具效果。这意味着什么呢?它涉及理解我们分析的特定领域,以从数据集中提取隐藏的见解。
可见信息通常很直观——比如缺失值、异常值、创建新变量或重新分类现有变量。但揭示隐藏的信息则需要更深入的方法。
这种分析层次通常只有在积累了一定经验并开始处理高级项目时才能实现。我们在这里的重点是应用基于我们领域特定知识的特征工程——在这个案例中,是医疗健康领域。
从五年的旅程中得到的四个收获
导航毕业后的学习之路
·发布于Towards Data Science ·5 分钟阅读·2024 年 10 月 4 日
--
自制图片。
9 月是一个充满新开始的月份—对一些人来说,甚至比 1 月还要重要—刚刚结束,我认为现在是分享我职业生涯中第一次经历的完美时机:在硕士毕业典礼上发表演讲!
当我有机会在几乎五年前我完成的同一硕士项目的毕业典礼上发表演讲时,我立即接受了。但是随后我开始慌乱—我应该说些什么?我有什么好的建议可以给新毕业生吗?
我几乎拖延到了最后一周,但最终意识到,在过去五年里,我在行业和学术界都有工作经验,我确实学到了四个关键的东西,这些东西如果在毕业后就知道的话,我会非常感激。
总体来说,这是一次不错的经历,现在我想和你们分享我的四个收获!
#1. 毕业是探索的最佳时机
为毕业演讲准备的自制幻灯片。
通过地理空间插值进行温度重建
你的电动汽车那么冷吗?
·发表于Towards Data Science ·阅读时长 7 分钟·2024 年 7 月 23 日
--
大气温度不仅仅是天气条件,它是电动汽车电池性能的重要决定因素。近期的事件突显了极低温度对电动汽车电池性能的严重影响,减少了车辆的续航里程,并可能导致充电问题。温度重建在电动汽车车队性能的事后分析中的作用不仅仅是一个理论概念,而是一个实际工具,可以显著提高我们对电动汽车电池性能的理解,并改善电动汽车电池性能,这是电动汽车行业的关键问题。
在本文中,我将介绍一种用于历史温度重建的实用方法。该方法利用时空插值和减少的测量点集,可应用于增强和改善我们对电动汽车(EV)电池性能的理解。
问题
我们希望重建超过 2200 万个带时间戳的位置的历史大气温度,而不向数据提供者发出相同数量的查询请求1。Open-Meteo 提供者对其免费访问层的每日请求数量进行限制,因此我们最好的选择是设计一个解决方案,以在保持良好精度的同时限制数据查询次数。
本文使用了扩展车辆能量数据集的原始 CSV 格式2。该数据集包含了 2017 年 11 月到 2018 年 11 月间在密歇根州安娜堡附近收集的车辆遥测数据。数据集包含了车辆测量的外部温度样本,但正如我们将看到的,这些数据有噪声,且不足以用于校准。
我们的问题是设计一个插值模型,该模型使用来自数据提供方的减少版本的真实温度集,并在目标数据上推导出高精度的预测。
解决方案
由于我们能从气象提供方获取的数据有限,我们将从遥测数据采样区域内的九个位置收集历史温度数据。我们收集了这九个位置全年每小时采样的温度时间序列,并将其作为插值的真实数据。
插值发生在三个维度中:两个是地图平面上的维度,第三个是时间维度。时间插值是最简单的,因为我们假设每小时样本之间的温度线性变化。正弦波可能是一个更好的模型,但可能代价太高,无法提高边际精度。我们使用反距离加权(IDW)[3,4]算法进行地理空间插值,利用每个温度源与目标点之间的反距离来估算目标温度。IDW 公式如下图图 1所示。
图 1 — IDW 温度估算器需要来自每个 n 个位置的线性插值温度(t),每个温度源与目标位置之间的距离(d),以及功率(p),这是一个可调参数。(图像由作者生成。)
如下图图 2所示,本解决方案使用了九个温度源。我们收集了数据提供方为每个位置报告的全年每小时的温度时间序列。这九个温度源的时间插值值将输入到地理空间插值器中,得到目标位置的估计温度的最终值。
图 2 - 上图显示了我们收集真实数据的九个位置。(图像由作者使用 OpenStreetMap 数据和影像创建。)
为了测试插值的准确性,我们还收集了源数据样本,直接调用气象提供方在这些采样位置获取数据,创建了一个验证数据集。我们从具有有效外部空气温度(OAT)信号的记录中选择了这些样本,以测试车辆报告的温度质量。
设置
首先,将文章的GitHub 仓库克隆到本地机器。要安装所有必要的依赖(Python 环境和所需的包),请从项目根目录运行安装程序,使用以下命令:
make install
如果你有访问 UNIX 系统的权限,可以直接运行以下命令行(请确保你对父目录具有写入权限,因为此脚本将尝试创建一个同级文件夹)来下载原始的 CSV 数据文件:
make download-data
如果您在 Windows 系统上运行,请阅读README文件中的说明。这些说明要求手动从源仓库下载数据并将压缩文件解压到适当的数据文件夹中。解压后,由于 CSV 编码错误,某些数据文件还需要特殊处理。
运行代码
该代码由三个 Python 脚本和共享的公共代码组成。运行说明请参见README文件。
通过运行此代码,您将从初始源数据集过渡到更新版,其中所有超过二千二百万行的数据都已标注上与地面真实值匹配的外部温度值。
python collect-samples.py
我们从一个脚本开始,该脚本确定在哪里收集年度的真实温度数据,并将其存储在文件缓存中以供后续使用(见图 2)。然后,它逐个 CSV 文件采样原始数据集,收集验证集以帮助调整 IDW 算法的幂参数。该脚本收集每一行采样的地面真实温度,假设 Open-Meteo 提供者报告了给定位置和日期的准确数据。脚本最后将包含地面真实温度数据的采样行保存为 CSV 文件,以便在第二步中重复使用。
python tune-idw-power.py
接下来,我们运行脚本,该脚本通过计算采样数据集的插值来调整 IDW 算法的幂参数,将参数值从一变到十。通过绘制幂值与计算出的 RMSE 之间的关系,我们找到了一个最小值为五(见图 3),我们将在最终脚本中使用该值。
图 2 — 上图展示了计算出的 RMSE 图表,RMSE 是地面真实温度和 IDW 算法插值结果之间的差值,随着幂参数值的变化,RMSE 值出现明显的最小值,当幂参数值为五时最小。(图像由作者生成。)
上面的 RMSE 值是通过将采样的温度作为真实值与插值后的温度进行比较计算得出的。该脚本计算了这些 RMSE 值,并且在与真实温度对比时,也计算了外部空气温度(OAT)信号的 RMSE。使用与上面图表相同的数据集,我们得到的值为 7.11,足足高出一个数量级,确认了 OAT 信号作为真实测量值的不足。
python update-temperatures.py
最终的脚本会遍历 CSV 输入文件,通过插值估算外部温度,并将结果保存到一组新的 CSV 文件中。新文件与输入文件相同,只是新增了一个温度列,并且已经准备好可以使用。
请注意,这个脚本运行时间较长。如果你决定中断它,它会在上次生成的文件之后恢复执行。
日期和时间处理
该数据集在处理日期时有一种非常特殊的方式。根据原始论文的 GitHub 仓库文档,每个数据点在两列中编码日期和时间。第一列DayNum
编码自数据收集开始以来的天数,其中值 1 对应第一天:
base_dt = datetime(year=2017, month=11, day=1,
tzinfo=timezone("America/Detroit"))
第二列Timestamp(ms)
编码了从旅行开始的毫秒偏移量(更多信息请参考仓库和论文)。要从这两列中获得有效日期,必须将基准日期与这两个偏移量相加,方式如下:
base_dt + timedelta(days=day_num-1) + timedelta(milliseconds=timestamp_ms)
每当我们需要将原始数据格式转换为标准的 Python 日期时间格式并明确指定时区时,你都会在代码中看到这种日期和时间的处理方式。
在从气象数据提供商收集温度信息时,我们明确要求所有的日期和时间都以当地时区表示。
结语
为什么在为本文编写代码时选择了Polars? Pandas 同样是一个有效的选择,但我想先试试这项新技术。尽管我发现它很容易上手,但它感觉不像是 Pandas 的直接替代品。Polars 有一些新的非常有趣的概念,比如懒处理,它在并行解析所有 CSV 文件并提取地理边界时非常有帮助。
Polars 的懒执行 API 让我想起了编程 Spark,这让我感到很怀念。我也想念 Pandas 的一些快捷方式,但它的速度提升和显然更好的 API 结构轻松弥补了这一点。
致谢
我使用了Grammarly来审阅文章,并接受了它的一些重写建议。
JetBrains 的 AI助手编写了部分代码,我还用它学习了 Polars。它已成为我日常工作的重要工具。
许可信息
扩展车辆能量数据集在 Apache 2.0 许可下发布,类似于其原始数据集 车辆能量数据集。
参考文献
2 Zhang, S., Fatih, D., Abdulqadir, F., Schwarz, T., & Ma, X. (2022). 扩展的车辆能量数据集 (eVED):一个用于深度学习的增强版大规模车辆行程能耗数据集。 ArXiv. /abs/2203.08630
4 逆距离加权. (2024 年 6 月 7 日). 载于 维基百科. en.wikipedia.org/wiki/Inverse_distance_weighting
João Paulo Figueira 是 tb.lx by Daimler Truck 的数据科学家,位于葡萄牙里斯本。
LLM 中的温度缩放与束搜索文本生成,面向机器学习相关领域的人
“温度”是什么,它如何工作,它与束搜索启发式算法的关系,以及 LLM 输出生成如何可能出错
·发表于Towards Data Science ·阅读时长 19 分钟·2024 年 4 月 26 日
--
由Paul Green拍摄,图片来源于Unsplash;除非另有说明,否则所有其他图片由作者提供
如果你曾使用过OpenAI或Anthropic等 LLM 的 API,你会看到 API 中有一个temperature
设置。这个参数是如何使用的,如何工作的呢?
temperature (number)
Amount of randomness injected into the response.
Defaults to 1.0\. Ranges from 0.0 to 1.0\. Use temperature closer to 0.0 for
analytical / multiple choice, and closer to 1.0 for creative and
generative tasks.
Note that even with temperature of 0.0, the results will not be
fully deterministic.
温度(按通常的实现方式)并不会真正注入随机性到响应中。在这篇文章中,我将讲解这个设置的作用,以及它如何在束搜索中使用,束搜索是 LLM 中最常用的文本生成技术,并通过GitHub 上的参考实现展示一些输出生成的例子(包括失败和成功的案例)。
你将要了解的内容:
-
重新审视 LLM 推理与 Token 预测
-
贪婪搜索
-
束搜索
-
温度
-
实现细节
-
贪婪搜索与束搜索生成示例
-
贪婪搜索
-
束搜索
-
带温度的束搜索
-
水牛水牛水牛水牛水牛水牛水牛水牛与得分处罚
-
-
结论
重访 LLM 推理和标记预测
如果你在这里,你可能对 LLM 是如何工作的有一些了解。
从高层次来看,LLM 文本生成涉及预测序列中的下一个标记,这取决于前面标记的累积概率。这个过程利用了由以下因素塑造的内部概率分布:
-
模型的内部学习权重,通过在庞大的数据集上进行广泛的训练得到了精细化。
-
整个输入上下文(查询以及任何其他补充数据或文档)
-
到目前为止生成的标记集
基于Transformer的生成模型通过自注意力机制构建输入上下文的表示,从而使它们能够动态评估并优先考虑输入的不同部分,基于这些部分与当前预测点的相关性。在序列解码过程中,这些模型评估每个部分如何影响正在生成的序列,确保每一个新的标记都能反映输入和不断演变的输出的整合(主要通过交叉注意力实现)。
斯坦福 CS224N 课程资料是理解这些概念的极好资源。
我在这里想要强调的关键点是,当模型决定选择概率上最优的标记时,它通常是在评估整个输入上下文,以及整个正在生成的序列。然而,使用这些预测来迭代构建文本序列的最直观过程是简化的:一个贪心算法,它在每一步基于最可能的标记构建输出文本。
接下来,我将讨论它是如何工作的、它的不足之处,以及一些用于适应这些不足的技术。
贪心搜索
使用模型构建输出序列的最自然方法是逐渐预测下一个最佳标记,将其附加到已生成的序列中,并继续直到生成结束。这叫做贪心搜索,是从 LLM(或其他模型)生成文本的最简单和最高效的方式。在最基本的形式下,它大致如下:
sequence = ["<start>"]
while sequence[-1] != "<end>":
# Given the input context, and seq so far, append most likely next token
sequence += model(input, sequence)
return "".join(sequence)
本科计算机科学算法课程中有一节关于图遍历算法的内容。如果你将潜在的 LLM 输出序列的宇宙建模为标记的图形,那么在给定输入上下文的情况下寻找最优输出序列的问题,便与遍历加权图的问题相似。在这种情况下,边的“权重”是由注意力分数生成的概率,而遍历的目标是最小化从头到尾的总体成本(最大化总体概率)。
贪心最佳优先搜索通过在每一步做出看似最佳的决定,以仅向前的方向遍历概念图标记。
在所有可能的文本生成方法中,这是最具计算效率的——推理次数与输出标记的数量是 1:1 的关系。然而,仍然存在一些问题。
在每一步的标记生成中,算法会根据目前为止的输出序列选择具有最高概率的标记,并将其附加到该序列中。这就是这种方法的简便之处,也是其缺陷,与所有其他贪心算法一样——它会陷入局部最小值。也就是说,看似当前最好的标记现在可能实际上不是生成输出整体上最好的标记。
"We can treat it as a matter of"
[course (p=0.9) | principle (p=0.5)] | cause (p=0.2)]"
给定一些输入上下文和目前生成的字符串,We can treat it as a matter of course
似乎是一个逻辑上合理且有可能生成的序列。
但如果上下文准确的句子是We can treat it as a matter of cause and effect
呢?贪心搜索无法回溯并将序列标记course
替换为cause and effect
。当时看似最好的标记实际上将输出生成困于一个次优序列中。
在每一步考虑低概率标记的需要,希望后续能生成更好的输出序列,这正是束搜索(beam search)有用的地方。
束搜索(Beam Search)
回到图搜索的类比,为了生成任何给定查询和上下文的最优文本,我们必须完全探索潜在的标记序列的宇宙。这个解决方案类似于A*搜索算法(比Dijkstra 算法更接近,因为我们不一定要最短路径,而是最低成本/最高可能性)。
A*搜索示意图由Wgullyn提供,来源于en.wikipedia.org/wiki/A*_search_algorithm
由于我们处理的是自然语言,涉及的复杂性太高,以至于在大多数情况下无法穷尽所有查询的搜索空间。解决方案是将搜索空间缩减到一个合理数量的候选路径;比如可能只有 4、8 或 12 条。
Beam search 是一种通常用于逼近理想 A* 搜索结果的启发式方法。该技术保持 k
个 候选序列,这些序列是通过分别选择 top-k 最可能的标记逐步构建的。每个标记都会贡献到整个序列的得分,在每一步后,所有候选序列会被修剪,只保留得分最高的前 k
个序列。
与 A* 搜索类似,Beam search 会保持从开始到结束的多条路径,评估有限数量候选序列的整体得分。这个数量被称为“beam width”。
Beam search 中的“beam” 借用了手电筒的类比,手电筒的光束可以被加宽或缩小。以生成 the quick brown fox jumps over the lazy dog 为例,假设 beam width 为 2
,整个过程大致如下:
在这一阶段,正在维护两个候选序列:“the” 和 “a”。这两个序列各自需要评估接下来最可能的两个标记。
下一步后,“the speedy” 被淘汰,“the quick” 被选为第一个候选序列。对于第二个候选序列,“a lazy” 被淘汰,“a quick” 被选中,因为它具有更高的累积概率。需要注意的是,如果线上两个候选的概率高于线下两个候选的概率,它们将代表下一步的两个候选序列。
这个过程会一直继续,直到达到最大标记长度限制,或者所有候选序列都已经添加了结束标记,意味着我们已经完成了该序列的文本生成。
增加 beam width 会增加搜索空间,从而提高输出更优结果的可能性,但也会相应地增加空间和计算成本。还需要注意的是,beam_width=1
的 beam search 实际上与贪心搜索是等同的。
Temperature
那么,temperature
与这一切有什么关系呢?正如我上面提到的,这个参数并不会真正的 注入随机性
到生成的文本序列中,但它确实会修改输出序列的 可预测性。借用 信息论的概念:temperature 可以增加或减少与标记预测相关的 熵。
Softmax 激活函数通常用于将模型(包括 LLM)的原始输出(即logits)转换为概率分布(我稍微讲解了一下这里)。该函数定义如下,给定一个包含n
个元素的向量Z
:
Sigma 通常用于指代 softmax 函数。
该函数输出一个概率向量(或张量),其总和为1.0
,可以用来清晰地评估模型在类别预测中的信心,且结果是人类可解释的。
可以引入一个“温度”缩放参数T
,该参数在应用 softmax 之前对 logit 值进行缩放。
温度缩放参数 T 应用于 softmax 函数的输入。
应用T > 1.0
的温度效果是缩小logit 值,并且产生了减弱不同类别之间概率最大差异的效果(它增加了模型预测中的熵)。
使用T < 1.0
的温度会产生相反的效果;它放大了差异,这意味着最有信心的预测会比其他预测更加突出。这减少了模型预测中的熵。
在代码中,它看起来是这样的:
scaled_logits = logits_tensor / temperature
probs = torch.softmax(scaled_logits, dim=-1)
看看给定一些手写 logit 值时,8 个可能类别的效果:
通过我链接的仓库中的脚本生成
上面的图是使用以下值绘制的:
ts = [0.5, 1.0, 2.0, 4.0, 8.0]
logits = torch.tensor([3.123, 5.0, 3.234, 2.642, 2.466, 3.3532, 3.8, 2.911])
probs = [torch.softmax(logits / t, dim=-1) for t in ts]
条形图表示 logit 值(模型预测的输出),而线条表示这些类别的概率分布,概率值定义在右侧标签上。粗红线表示期望的分布,温度为T=1.0
,而其他线条则演示了在0.5
到8.0
的温度范围内,相对可能性变化的情况。
你可以清楚地看到,T=0.5
强调了最大幅度 logit 索引的可能性,而T=8.0
则将类别之间的概率差异缩小到几乎为零。
>>> [print(f' t={t}\n l={(logits/t)}\n p={p}\n') for p,t in zip(probs, ts)]
t=0.5
l=tensor([6.2460, 10.000, 6.4680, 5.2840, 4.9320, 6.7064, 7.6000, 5.8220])
p=tensor([0.0193, 0.8257, 0.0241, 0.0074, 0.0052, 0.0307, 0.0749, 0.0127])
t=1.0
l=tensor([3.1230, 5.0000, 3.2340, 2.6420, 2.4660, 3.3532, 3.8000, 2.9110])
p=tensor([0.0723, 0.4727, 0.0808, 0.0447, 0.0375, 0.0911, 0.1424, 0.0585])
t=2.0
l=tensor([1.5615, 2.5000, 1.6170, 1.3210, 1.2330, 1.6766, 1.9000, 1.4555])
p=tensor([0.1048, 0.2678, 0.1108, 0.0824, 0.0754, 0.1176, 0.1470, 0.0942])
t=4.0
l=tensor([0.7807, 1.2500, 0.8085, 0.6605, 0.6165, 0.8383, 0.9500, 0.7278])
p=tensor([0.1169, 0.1869, 0.1202, 0.1037, 0.0992, 0.1238, 0.1385, 0.1109])
t=8.0
l=tensor([0.3904, 0.6250, 0.4042, 0.3302, 0.3083, 0.4191, 0.4750, 0.3639])
p=tensor([0.1215, 0.1536, 0.1232, 0.1144, 0.1119, 0.1250, 0.1322, 0.1183])
现在,这不一定会改变任何两个类别之间的相对可能性(除去数值稳定性问题),那么它在序列生成中有什么实际影响呢?
答案在于束搜索的机制。温度值大于1.0
使得高分的单个标记不太可能超过一系列稍微不太可能的标记,二者结合起来产生一个更高分的输出。
>>> sum([0.9, 0.3, 0.3, 0.3]) # raw probabilities
1.8 # dominated by first token
>>> sum([0.8, 0.4, 0.4, 0.4]) # temperature-scaled probabilities
2.0 # more likely overall outcome
总结来说,更高的温度设置允许束搜索在标记图上探索更多样化的候选序列路径。较低的温度设置使其越来越专注于每一步最可能的预测。
实现细节
束搜索实现通常使用对数概率来处理 softmax 概率,这在机器学习领域及其他许多领域中都很常见。其原因包括:
-
使用的概率通常极其小;使用对数概率可以改善数值稳定性。
-
我们可以通过对数概率的加法来计算结果的累积概率,而不是直接乘以原始概率,这在计算上稍微更快,并且数值上更稳定。回忆一下,
p(x) * p(y) == log(p(x)) + log(p(y))
-
优化器,如梯度下降,在处理对数概率时更为简单,这使得导数计算更加简便,而交叉熵损失等损失函数已经涉及了对数计算。
这也意味着我们作为评分使用的对数概率值是负实数。由于 softmax 产生的概率分布的总和为1.0
,因此任何类别的概率的对数值都将≤ 1.0
,从而产生负值。这有些烦人,但它与高评分值更好这一特性一致,而极其负的评分则反映了极不可能的结果:
>>> math.log(3)
1.0986122886681098
>>> math.log(0.99)
-0.01005033585350145
>>> math.log(0.98)
-0.020202707317519466
>>> math.log(0.0001)
-9.210340371976182
>>> math.log(0.000000000000000001)
-41.44653167389282
这里是大部分示例代码,注释非常详细,代码也可以在Github上找到。GeneratedSequence
和ScoredToken
的定义可以在这里找到;这些主要是用于包装标记和得分的简单工具。
# The initial candidate sequence is simply the start token ID with
# a sequence score of 0
candidate_sequences = [
GeneratedSequence(tokenizer, start_token_id, end_token_id, 0.0)
]
for i in tqdm.tqdm(range(max_length)):
# Temporary list to store candidates for the next generation step
next_step_candidates = []
# Iterate through all candidate sequences; for each, generate the next
# most likely tokens and add them to the next-step sequnce of candidates
for candidate in candidate_sequences:
# skip candidate sequences which have included the end-of-sequence token
if not candidate.has_ended():
# Build a tensor out of the candidate IDs; add a single batch dimension
gen_seq = torch.tensor(candidate.ids(), device=device).unsqueeze(0)
# Predict next token
output = model(input_ids=src_input_ids, decoder_input_ids=gen_seq)
# Extract logits from output
logits = output.logits[:, -1, :]
# Scale logits using temperature value
scaled_logits = logits / temperature
# Construct probability distribution against scaled
# logits through softmax activation function
probs = torch.softmax(scaled_logits, dim=-1)
# Select top k (beam_width) probabilities and IDs from the distribution
top_probs, top_ids = probs.topk(beam_width)
# For each of the top-k generated tokens, append to this
# candidate sequence, update its score, and append to the list of next
# step candidates
for i in range(beam_width):
# the new token ID
next_token_id = top_ids[:, i].item()
# log-prob of the above token
next_score = torch.log(top_probs[:, i]).item()
new_seq = deepcopy(candidate)
# Adds the new token to the end of this sequence, and updates its
# raw and normalized scores. Scores are normalized by sequence token
# length, to avoid penalizing longer sequences
new_seq.append(ScoredToken(next_token_id, next_score))
# Append the updated sequence to the next candidate sequence set
next_step_candidates.append(new_seq)
else:
# Append the canddiate sequence as-is to the next-step candidates
# if it already contains an end-of-sequence token
next_step_candidates.append(candidate)
# Sort the next-step candidates by their score, select the top-k
# (beam_width) scoring sequences and make them the new
# candidate_sequences list
next_step_candidates.sort()
candidate_sequences = list(reversed(next_step_candidates))[:beam_width]
# Break if all sequences in the heap end with the eos_token_id
if all(seq.has_ended() for seq in candidate_sequences):
break
return candidate_sequences
在下一部分,你可以找到在不同数据集上使用不同参数运行此代码的一些结果。
贪婪搜索和束搜索生成示例
正如我提到的,我已经将一些示例代码发布到 Github,该代码使用了t5-small
Hugging Face 的 transformer 模型及其对应的T5Tokenizer。下面的示例是通过 T5 模型运行的,使用了quick brown fox 等维基百科页面,经过提取脚本的清洗。
贪婪搜索
运行--greedy
模式:
$ python3 src/main.py --greedy --input ./wiki-fox.txt --prompt "summarize the following document"
greedy search generation results:
[
the phrase is used in the annual Zaner-Bloser National Handwriting Competition.
it is used for typing typewriters and keyboards, typing fonts. the phrase
is used in the earliest known use of the phrase.
]
该输出很好地总结了文章的部分内容,但整体效果不佳。它缺乏初始上下文,有重复内容,并且没有明确说明短语的具体含义。
束搜索
让我们再试一次,这次使用波束搜索进行输出生成,初始波束宽度为4
,并且使用默认的temperature
值1.0
。
$ python3 src/main.py --beam 4 --input ./wiki-fox.txt --prompt "summarize the following document"
[lots of omitted output]
beam search (k=4, t=1.0) generation results:
[
"the quick brown fox jumps over the lazy dog" is an English-language pangram.
the phrase is commonly used for touch-typing practice, typing typewriters and
keyboards. it is used in the annual Zaner-Bloser National
Handwriting Competition.
]
这个输出远优于上述的贪婪输出,最值得注意的是我们使用相同的模型、提示和输入上下文来生成它。
其中仍然有一些错误;例如“打字打字机”,并且“键盘”可能存在歧义。
我分享的波束搜索代码将会输出其在文本生成过程中逐步决策的进展(完整输出在这里)。例如,前两步:
beginning beam search | k = 4 bos = 0 eos = 1 temp = 1.0 beam_width = 4
0.0: [], next token probabilities:
p: 0.30537632: ▁the
p: 0.21197866: ▁"
p: 0.13339639: ▁phrase
p: 0.13240208: ▁
next step candidates:
-1.18621039: [the]
-1.55126965: ["]
-2.01443028: [phrase]
-2.02191186: []
-1.1862103939056396: [the], next token probabilities:
p: 0.61397356: ▁phrase
p: 0.08461960: ▁
p: 0.06939770: ▁"
p: 0.04978605: ▁term
-1.5512696504592896: ["], next token probabilities:
p: 0.71881396: the
p: 0.08922042: qui
p: 0.05990228: The
p: 0.03147057: a
-2.014430284500122: [phrase], next token probabilities:
p: 0.27810165: ▁used
p: 0.26313403: ▁is
p: 0.10535818: ▁was
p: 0.03361856: ▁
-2.021911859512329: [], next token probabilities:
p: 0.72647911: earliest
p: 0.19509122: a
p: 0.02678721: '
p: 0.00308457: s
next step candidates:
-1.67401379: [the phrase]
-1.88142237: ["the]
-2.34145740: [earliest]
-3.29419887: [phrase used]
-3.34952199: [phrase is]
-3.65579963: [the]
-3.65619993: [a]
现在,如果我们看看最后一步的候选集合:
next step candidates:
-15.39409454: ["the quick brown fox jumps over the lazy dog" is an English-language pangram. the phrase is commonly used for touch-typing practice, typing typewriters and keyboards. it is used in the annual Zaner-Bloser National Handwriting Competition.]
-16.06867695: ["the quick brown fox jumps over the lazy dog" is an English-language pangram. the phrase is commonly used for touch-typing practice, testing typewriters and keyboards. it is used in the annual Zaner-Bloser National Handwriting Competition.]
-16.10376084: ["the quick brown fox jumps over the lazy dog" is an English-language pangram. the phrase is commonly used for touch-typing practice, typing typewriters and keyboards. it is used in the annual Zaner-Bloser national handwriting competition.]
你可以看到,包含打字打字机
的得分最高的句子比包含测试打字机
的句子高出-15.39
到-16.06
,如果我们提高到欧拉常数并转换回累积概率,这是一个仅为0.00001011316%
的概率差异。必须有办法克服这个微小的差异!
带温度的波束搜索
让我们看看是否通过应用温度值来平滑一些对数概率得分,能够改善这个总结。再次强调,其他一切,模型和输入上下文,将与上述示例完全相同。
$ python3 src/main.py --beam 4 --temperature 4.0 --input ./wiki-fox.txt --prompt "summarize the following document"
[lots of omitted output]
beam search (k=4, t=4.0) generation results:
[
"the quick brown fox jumps over the lazy dog" is an English-language pangram.
it is commonly used for touch-typing practice, testing typewriters and
computer keyboards. earliest known use of the phrase started with "A"
]
这个输出正确地生成了“测试打字机”而不是“打字打字机”,并且明确指定了“计算机键盘”。有趣的是,它选择了历史事实,即这个短语最初以“一只快速的棕色狐狸”开始,而不是上面提到的 Zaner-Bloser 竞赛事实。完整的输出也可以在这里找到。
这个输出是否更好是一个主观的看法问题。它在一些细微的方面有所不同,温度值的使用和设置会因应用而异。我认为它更好,而且有趣的是,获得这个输出时没有改变任何模型权重、模型架构或提示。
水牛水牛水牛水牛水牛水牛水牛水牛与评分惩罚
让我们看看波束搜索,使用上述温度设置,是否能正确处理我最喜欢的英语语言学构造:水牛水牛水牛水牛水牛水牛水牛水牛。
$ python3 src/main.py --beam 4 --temperature 4.0 --input ./wiki-buffalo.txt --prompt "summarize the linguistic construct in the following text"
[lots of omitted outputs]
beam search (k=4, t=4.0) generation results:
[
"Buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo
buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo
buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo
buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo
buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo
buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo
buffalo buffalo buffalo buffalo buffalo buffalo
]
完全的灾难,尽管这是可以预见的。鉴于这个输入文档的复杂性,我们需要额外的技术来处理像这样的上下文。有趣的是,最后一次迭代的候选并没有包括任何一个合理的序列:
next step candidates:
-361.66266489: ["Buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo]
-362.13168168: ["buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo]
-362.22955942: ["Buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo.]
-362.60354519: ["Buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo]
-363.03604889: ["Buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo,]
-363.07167459: ["buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo]
-363.14155817: ["Buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo Buffalo]
-363.28574753: ["Buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo. the]
-363.35553551: ["Buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo a]
[more of the same]
我们可以 应用特定令牌的得分衰减(更像是 惩罚)对重复的令牌进行处理,这使得它们在束搜索算法中显得不那么有吸引力(或者更准确地说,更不可能的解):
token_counts = Counter(t.token_id for t in candidate)
# For each of the top-k generated tokens, append to this candidate sequence,
# update its score, and append to the list of next step candidates
for i in range(beam_width):
next_token_id = top_ids[:, i].item() # the new token ID
next_score = torch.log(top_probs[:, i]).item() # log-prob of the above token
# Optionally apply a token-specific score decay to repeated tokens
if decay_repeated and next_token_id in token_counts:
count = token_counts[next_token_id]
decay = 1 + math.log(count + 1)
next_score *= decay # inflate the score of the next sequence accordingly
new_seq = deepcopy(candidate)
new_seq.append(ScoredToken(next_token_id, next_score))
这导致了以下更合理的输出:
$ python3 src/main.py --decay --beam 4 --temperature 4.0 --input ./wiki-buffalo.txt --prompt "summarize the linguistic construct in the following text"
[lots of omitted outputs]
beam search (k=4, t=4.0) generation results:
[
"Buffalo buffalo" is grammatically correct sentence in English, often
presented as an example of how homophonies can be used to create complicated
language constructs through unpunctuated terms and sentences. it uses three
distinct meanings:An attributive noun (acting
]
你可以看到得分惩罚将 无限水牛 序列拉低,导致了上述输出结果:
next step candidates:
-36.85023594: ["Buffalo buffalo Buffalo]
-37.23766947: ["Buffalo buffalo"]
-37.31325269: ["buffalo buffalo Buffalo]
-37.45994210: ["buffalo buffalo"]
-37.61866760: ["Buffalo buffalo,"]
-37.73602080: ["buffalo" is]
[omitted]
-36.85023593902588: ["Buffalo buffalo Buffalo], next token probabilities:
p: 0.00728357: ▁buffalo
p: 0.00166316: ▁Buffalo
p: 0.00089072: "
p: 0.00066582: ,"
['▁buffalo'] count: 1 decay: 1.6931471805599454, score: -4.922133922576904, next: -8.33389717334955
['▁Buffalo'] count: 1 decay: 1.6931471805599454, score: -6.399034023284912, next: -10.834506414832013
-37.237669467926025: ["Buffalo buffalo"], next token probabilities:
p: 0.00167652: ▁is
p: 0.00076465: ▁was
p: 0.00072227: ▁
p: 0.00064367: ▁used
-37.313252687454224: ["buffalo buffalo Buffalo], next token probabilities:
p: 0.00740433: ▁buffalo
p: 0.00160758: ▁Buffalo
p: 0.00091487: "
p: 0.00066765: ,"
['▁buffalo'] count: 1 decay: 1.6931471805599454, score: -4.905689716339111, next: -8.306054711921485
['▁Buffalo'] count: 1 decay: 1.6931471805599454, score: -6.433023929595947, next: -10.892056328870039
-37.45994210243225: ["buffalo buffalo"], next token probabilities:
p: 0.00168198: ▁is
p: 0.00077098: ▁was
p: 0.00072504: ▁
p: 0.00065945: ▁used
next step candidates:
-43.62870741: ["Buffalo buffalo" is]
-43.84772754: ["buffalo buffalo" is]
-43.87371445: ["Buffalo buffalo Buffalo"]
-44.16472149: ["Buffalo buffalo Buffalo,"]
-44.30998302: ["buffalo buffalo Buffalo"]
所以事实证明,我们需要像这样的额外技巧(技术),来处理一些特殊的边缘情况。
结论
这篇文章比我原本计划写的要长很多;希望你能从中得到一些启示。除了单纯理解束搜索(beam search)和温度(temperature)是如何工作的之外,我认为最有趣的例子是,即使考虑到 LLM 的巨大复杂性和能力,影响它们预测结果使用方式的实现选择,仍然对输出质量产生了巨大影响。将简单的本科计算机科学概念应用于序列构建,可以导致显著不同的 LLM 输出,即使其他所有输入完全相同。
当我们在使用 LLM 时遇到幻觉、错误或其他怪癖时,完全有可能(也许很可能)这些问题是由输出序列构建算法的怪癖引起的,而不是训练模型本身的“故障”。对于 API 的用户来说,几乎不可能分辨出差异。
我认为这是一个有趣的例子,展示了 LLM 背后复杂的机制,使它们成为今天如此强大的工具和产品。
时间差分学习:将动态规划与蒙特卡罗方法结合用于强化学习
强化学习的里程碑:Q 学习与双重 Q 学习
·发表于Towards Data Science ·15 分钟阅读·2024 年 10 月 17 日
--
我们继续深入研究 Sutton 的书《强化学习:导论》1,在这篇文章中介绍时间差分(TD)学习,这是该书的第六章内容。
TD 学习可以看作是动态规划(DP)与蒙特卡罗(MC)方法的结合,我们在前两篇文章中介绍了这两种方法,并且标志着强化学习(RL)领域的一个重要里程碑——结合了上述方法的优点:TD 学习不需要模型,仅从经验中学习,类似于 MC,但也具有“自举”——使用先前的估计值,类似于 DP。
图片来源:Brooke Campbell 在Unsplash
在这里,我们将介绍这类方法的理论基础,并展示相关的实用算法,如 Q 学习——并附上 Python 代码。与往常一样,所有代码都可以在GitHub上找到。
我们首先介绍背景和动机,然后从预测问题开始——与之前的帖子类似。接着,我们深入探讨理论,并讨论 TD 学习找到的解决方案。随后,我们转向控制问题,并提出…
2024 年时序图学习
继续探索不断发展的网络
·发布于数据科学前沿 ·阅读时间 20 分钟·2024 年 1 月 18 日
--
许多复杂的网络随着时间的推移而演化,包括交易网络、交通网络、社交网络等。时序图学习(TGL)是一个快速发展的领域,旨在学习、预测和理解这些不断发展的网络。请参阅我们的上一篇博客文章,了解时序图学习的介绍以及去年的一些进展。
在 2023 年,我们看到学术界和工业界对 TGL(时序图学习)发展的兴趣显著增加。与去年相比,NeurIPS 2023 时序图学习研讨会的投稿数量增长了三倍,最终有 35 篇论文被接收。此外,2023 年 2 月启动的时序图学习读书小组至今已举办了 28 场研究报告(可以在YouTube上找到录音)。目前,已有近 200 名研究人员注册加入读书小组,我们很高兴看到该主题受到了关注,并且社区极其活跃。
图片由作者提供,通过DALL.E 3生成
本文由 Emanuele Rossi, Michael Galkin, Andrea Cini 和 Ingo Scholtes 共同撰写。
本文涵盖了时序图学习(TGL)领域的一些激动人心的发展,同时指出了 2024 年的研究方向。我们还邀请了领先的研究人员分享他们对时序图学习未来发展的看法。本文还旨在为那些希望深入了解时序图学习的读者提供参考资料,并作为学习起点。欢迎在评论区与我们分享你感兴趣的其他进展。如需了解更多图学习的进展,请查看Michael Galkin的精彩博文。
目录:
-
时序图基准
-
链接预测的创新架构
-
时空图和图深度学习在时间序列处理中的应用
-
时序知识图谱
-
因果感知的时序图学习
-
可解释的时序图方法
-
对时序图的对抗攻击
-
库和基准
-
加入时序图学习社区
时序图基准
图学习快速发展的驱动力之一是标准化和多样化基准的出现,如开放图基准(OGB)、长程图基准和GraphWorld。然而,这些基准是为静态图设计的,缺乏进行时序图学习所需的精细时间戳信息。因此,时序图学习的进展受限于缺乏大型高质量数据集,以及缺乏适当的评估方法,导致性能过于乐观。
为了填补这一空白,时序图基准(TGB)最近被提出,包括一系列具有挑战性和多样化的基准数据集,用于对时序图上的机器学习进行真实、可重现和鲁棒的评估。TGB 提供了一个pypi 包,可以自动下载和处理来自五个不同领域的九个数据集,数据集包含最多 7200 万个边和 3000 万个时间戳。TGB 还提供了基于真实应用的标准化评估。
TGB:时序图学习的挑战性和真实基准。
图片来源:黄等,2023,作者提供。
TGB 包含了链接和节点级任务,并对所有数据集上的最先进 TG 模型进行了广泛的实证比较。第一个任务是动态链接属性预测任务,预测未来某一时刻一对节点之间的链接属性(通常是存在性)。在 TGB 中,这个任务被建模为一个排序问题,并通过过滤后的平均倒数排名(MRR)指标进行评估。结果表明,模型排名在不同数据集上差异显著,数据集中的测试集边缘比例在训练过程中从未出现过。此外,随着更多负样本(不存在的边)用于评估,模型的表现会下降。有趣的是,表现下降的程度在不同模型之间也有所不同。
在动态节点属性预测任务中,目标是预测给定时间节点的属性。更具体地,我们关注节点亲和力预测任务,该任务建模用户对不同物品的偏好随时间的变化。在这里,我们使用前 10 个物品的标准化折扣累积增益(NDCG@10)来比较预测物品的相对顺序与真实值的顺序。有趣的是,我们发现单一启发式方法优于现有的 TG 模型,这突显了未来需要更多专注于节点级任务的模型。TGB 排行榜是公开的,欢迎通过Google 表单提交您的模型。更多详情,请参阅TGB 博客文章,由该博客的作者提供。
链接预测的新架构
“在时序图学习领域,链接预测提出了巨大的挑战。学习算法必须超越传统信息传递架构(如 GNNs)通常所具有的有限表现力。此外,它们还必须强调计算效率。一个关键方面是确保在响应链接预测查询时具有低延迟,在动态和复杂的数据环境中平衡模型的表现力与预测速度。” —— 潘力,乔治亚理工学院助理教授
Longa et al.最近的调查提供了关于时间 GNN 的全面概述。许多方法提出了专门的架构用于动态链接预测,通常旨在捕获重要的结构属性或相关性。例如,Luo et al.旨在显式地建模一组节点的联合邻域,用于未来的链接预测,他们设计了Neighborhood-Aware Temporal 网络模型(NAT)。传统的图神经网络(GNN)方法无法捕获联合邻域,因为节点嵌入向量是为每个节点独立生成的。在下面的示例中,节点v和w具有相同的结构上下文,因此在 GNN 眼中是不可区分的。实际上,由于三元闭合法则,节点u和v在 t₃时的链接更可能形成,而节点u和w在 t₃时的链接则不确定。相比之下,NAT 采用了一种新的字典型邻域表示,记录 k 跳邻域信息,并允许快速构建多个节点的联合邻域的结构特征。字典表示通过一种名为 N-cache 的高效缓存技术进行维护。N-cache 使 NAT 能够为一批节点对快速构建联合邻域特征,用于快速链接预测。
GNN 嵌入的节点v和 w 将是相同的。
图片来源:Luo et al. 2022
其次,Yu et al.旨在通过提出 DyGFormer,一种基于 Transformer 的时间图学习架构,捕获长期时间依赖性。给定时间t时节点u和节点v之间的查询,第一步是提取时间t之前节点u和v的历史第一跳交互。这包括邻居的编码、链接、时间间隔以及每个邻居在u和v中的出现频率。假设如果u和v在过去有更多共同的历史邻居,那么它们在未来更可能发生交互。将历史交互编码为一个序列后,接下来将其划分为多个补丁,并输入到 Transformer 中以捕获时间依赖性。
DyGFormer 框架
图片来源:Yu et al. 2023
另一个问题是 我们真的需要复杂的模型架构来处理时间网络吗? 在一篇同名论文中,Cong 等人考察了在时间图学习中常用模块(如递归神经网络(RNN)和自注意力机制)的必要性。他们表明,这些模块并不总是动态链接预测所必需的。特别地,他们提出的 GraphMixer 模型完全基于多层感知机(MLP)和邻居均值池化,并在与包含 RNN 和自注意力机制的基准模型比较时表现出色。GraphMixer 包含三个模块:链接编码器总结来自时间链接的信息,节点编码器提取节点的信息,链接分类器结合以上信息进行预测。有趣的是,Cong 等人认为可训练的时间编码函数可能在训练过程中导致不稳定,因此选择了固定时间编码函数 z(t) = cos(tω),其中固定特征 捕捉 了两个时间戳之间的相对差异,如下所示。
固定时间编码函数将 t 转换为向量 cos(tω)。
x 轴表示向量维度,y 轴表示余弦值。
图像来源:Cong 等人 2023
最后,Suresh 等人指出,现有方法在未来链接上独立最大化准确度,忽略了未来链接之间通常存在依赖关系的事实。这种情况在用户从一系列商品中选择购买物品或在社交网络中选择一组用户进行连接时尤为明显。因此,Suresh 等人将动态链接预测视为一个排序问题,并提出了Temporal Graph 网络用于RANKing(TGRank),通过该方法学习对候选项进行排名。TGRank 的流程图如下所示。任务查询现在包含一个中心节点 s(在示例中)以及一组候选节点(子图中的所有其他节点),目标是对最可能的候选项进行排名,作为节点 s 的目标。为此,TGRank 遵循三个步骤。首先,节点 s 被与其他节点标注不同。然后,使用 GNN(图神经网络)将中心节点标签传播到每个排序候选项。这个参数化的标签传播步骤聚合了时间戳、重复性以及沿着网络从中心节点到所有候选项的历史交互特征,并为链接预测提供了明显更强的表达能力。最后,使用基于列表的损失函数来联合优化候选项之间的排名。从经验上看,使用列表式排名损失后,像TGN和TGAT等流行模型的表现也优于其原始设置中的二分类损失。
TGRank 的流程图。中心节点是节点s。
图像来源:Suresh et al. 2023
时空图与图深度学习在时间序列处理中的应用
“将我们的预测主要基于最相关的观察结果是明智的,但并不总是简单的,因为相关的数据关系常常隐藏在显而易见的地方。揭示这些关系是一个引人入胜的挑战,尤其是当它们是动态的,或者涉及超过两个实体时。” — Daniele Zambon,瑞士人工智能实验室 IDSIA 的博士后
在时序图学习社区中,时空图这一术语通常用来表示具有固定拓扑结构和随时间变化的节点特征的图,通常是在离散的时间步长上,这些时间步长对应于定期采样的观测数据。最近,处理具有这种结构的数据问题正在从不同的角度进行考虑,即将动态节点特征视为时间序列,而将边视为观测序列之间的函数依赖关系(Cini et al. 2023,Ming et al. 2023)。从这一角度来看,时空图表示法允许通过利用图神经网络的架构偏向来处理关联时间序列集合,这一视角与本文讨论的许多其他设定有显著的偏离。
具有关联侧信息的时间序列
图像来源:Cini et al. 2023,作者提供。
这样的关联时间序列集可以通过传感器生成,无论是物理传感器还是非物理传感器。例如,在交通领域,时间序列可能对应于传感器读取的数据,这些传感器测量的是在交叉口通过的车辆数量。每个传感器将对应一个不同的节点,邻接矩阵可以通过与仅通过路段直接连接的传感器连接边来获得。除了交通预测(Li et al. 2018,Yu et al. 2018),这些表示法还广泛应用于时间序列处理的各类应用中,涵盖了从空气质量监测(Chen et al. 2021)和能源分析(Cini et al. 2023)到生物医学数据处理(Zhang et al. 2022)和金融时间序列分析(Matsunaga et al. 2019)等各个领域。
来自交通领域的关联时间序列示例。
图像来源:ECML PKDD 2023 教程,作者提供。
为了处理这些数据,标准的消息传递框架需要更新,以处理来自每个节点邻域的观测序列。这可以通过替换适当的操作符(即消息和更新函数)为能够沿时间维度处理数据的操作符来轻松实现,例如递归单元(Seo et al. 2018),时空卷积(Wu et al. 2019)和基于注意力的架构(Marisca et al. 2022)。由此产生的模型被称为时空图神经网络(STGNNs),并且已经有大量研究致力于提出有效的架构(见 Ming et al. 2023)。使用 STGNNs 的主要优势之一是可以使用相同的参数集来预测任何子集的时间序列,同时在处理过程中考虑到依赖关系。这比标准的多元时间序列预测模型具有巨大的优势,因为后者通常需要单独预测每个时间序列或放弃参数共享。也可以考虑使用混合型 STGNNs,带有一些特定于时间序列的参数,正如我们在a recent NeurIPS paper中所展示的,往往优于那些共享所有参数的模型。除了模型架构之外,基于图的表示,如Zambon et al.所示,也可以通过将时空相关性分析聚焦于相互连接的节点,帮助评估预测模型的最优性。
一个时空图神经网络。
作者提供的图片。
该领域固有的几个挑战,包括处理不规则采样的时间序列和缺失数据;实际上,这两种现象在处理实际的网络物理系统时非常常见。幸运的是,图模型在这一背景下也很有用,例如可以基于相邻传感器的观察结果来条件化重建。可扩展性是另一个主要问题,因为与标准的图神经网络不同,消息传递通常是针对每个时间步进行的。现有的可扩展架构主要依赖于子采样和/或预计算的节点特征。当没有先验关系信息时,挑战就变成了如何直接从时间序列中学习潜在图。例如,问题已经被通过直接学习邻接矩阵(例如,Wu 等,2019)或在概率框架下,依赖于重参数化技巧和基于评分的估计器来解决。
可扩展的时空图神经网络
图片来源:Cini 等,2023。由作者提供
由于去年博客中没有涉及这一主题,因此本文的目的是简要概述相关设置以及可以建模的问题。目前有很多方向正在探索,包括图状态空间模型、图卡尔曼滤波器、基于扩散的模型以及连续时空模型。如果你有兴趣了解更多内容和/或在实践中使用这些模型,我们最近发布了一篇关于该主题的综合教程论文。你还可以查看我们的库Torch Spatiotemporal (tsl),它用于构建基于图的时间序列处理管道。
时序知识图
令人惊讶的是,今年在顶级机器学习会议上有很少的时序 KG 论文:TILP(Xiong 等,2023)探讨了与神经方法竞争的时序规则学习,以及Chen 和 Wang的理论研究,旨在衡量时序 GNN 的表现力。事实上,最有趣的(对我来说)这类论文是在 NeurIPS’23 的 TGL 研讨会上找到的(这也是你应该关注该会议的一个原因!),例如,Pop 和 Kostylev预测未来时间间隔,或Pan 等人在标准基准数据集中识别数据泄露。最后,我想提到Ning 等人的统一城市知识图谱(UUKG),它为时序 KG 数据集提供了一个全新的视角,真正具有实际意义并且有应用场景——建模城市中的交通流。
UUKG 展示了时序知识图谱(KG)社区萎缩的最大问题——缺乏实际重要的任务和数据集,在这些任务中可以展示数据建模范式在现实世界任务中的实用性。也就是说,与几何深度学习在生物学或材料科学中的成功相比(或者与大型语言模型相比,但那是另一个话题),在 10 年历史的 KG 嵌入基准上增加 1%的 MRR/Hits@10 如今显得毫无意义。希望未来我们能看到更多类似 UUKG 的实用数据集。
也许时序 KG 可能产生影响的另一个相关领域是异构图(通常具有类型化边缘),这些图在工业界的使用频率更高。例如,最近的RelBench(关系深度学习基准)提出了一个基于关系数据库的时序预测问题,这个问题可以轻松转化为 KG 或超图。
因果感知的时序图学习
“爱因斯坦说,时间的箭头只朝一个方向飞去。[……]在我们当中,有谁愿意放弃重温那个曾让我们第一次体会到爱、狂喜,或做出一个永远改变我们未来的选择的日子或时刻,抹去可能曾有过的一种生活?这样的机会是很少有的。”——格雷格·艾尔斯,《安静的游戏》
时间图学习之所以有趣,部分原因是它——根据手头的数据——需要不同的视角。例如,考虑具有高分辨率(可能是连续)时间戳的时间图数据。在这种数据中,利用快照图序列的离散时间图学习技术需要对时间进行粗粒化,其中每个快照由在某个时间间隔内发生的边缘组成。这种粗粒化使得可以将(静态)图学习技术推广到时间序列数据。但它也带来了一个主要问题:每个快照都会丢弃关于边缘发生的时间顺序的信息,而时间顺序是因果或时间尊重路径的基础(Kempe et al. 2000)。像静态图中的路径一样,时间尊重路径很重要,因为它们告诉我们哪些节点可以间接地因果影响彼此。下面,我们通过一个简单的时间图进行说明,其中包含两条无向边(a,b)和(b,c),分别发生在时间t₁和t₂。如果(a,b)发生在(b,c)之前,则a可以通过一条时间尊重路径(紫色路径)因果影响c,该路径经过b。如果边的时间顺序颠倒,a则无法因果影响c,因为任何影响必须在时间上逆向传播。请注意,a到c的影响是有方向的,这是由于时间的箭头方向,尽管这两条边都是无向的。此外,虽然在静态的、时间聚合图中,边(a,b)和(b,c)表示从a通过b到c的传递路径(紫色)以及反向路径(橙色),但在时间图中并非如此。
从节点 a 到节点 c 的时间尊重路径。
图片来自作者。
多项研究表明——由于时间的箭头——时间图的因果拓扑,即哪些节点可以通过时间尊重路径因果影响彼此,与静态图相比有很大不同,这对流行病传播(Pfitzner et al. 2013)、扩散速度(Scholtes et al. 2014)、节点中心性(Rosvall et al. 2014)或社区检测(Lambiotte et al. 2019)具有重要的影响。今年提出的进展表明,可以基于模型实现这一目标,这些模型将常用的时间图静态表示进行了一般化。考虑一个加权的时间聚合图,其中一条(有向)边(a,b)的权重为 5,表示(a,b)在时间图中出现了五次。下图中 2 面板的底部展示了这种加权的时间聚合图。
预测时间图中节点时间中心性的流程图。
图片来源:Heeg 等人,作者提供
时间图中的每条边都是长度为一的时间遵循路径。因此,经过加权时间聚合的图对应于时间图因果拓扑的一个一阶模型,能够捕捉时间遵循的长度为一的路径。它忽略了边的时间顺序,因为我们仅计算每条边出现的频率。通过图形变换,我们可以将这一思想推广到因果感知模型,以促进时间图的学习:我们只需将一阶图中的边替换为二阶图中的节点,即将边(a,b)和(b,c)转换为节点“a→b”和“b→c”。在生成的二阶图中(参见图 2 面板顶部图示),我们可以使用边表示长度为二的时间遵循路径,即边(a→b, b→c)表示a通过b因果地影响c。然而,边的反向顺序不被包含。如果边按相反顺序出现,我们就不会包含(a→b, b→c)。重要的是,这样的二阶图对边的时间顺序敏感,而一阶图则不敏感!在Scholtes, 2017中,这一思想被推广到更高阶,从而产生了k 阶德·布鲁因图模型,用于时间图的因果拓扑。
Qarkaxhija 等人已证明,在此类高阶德·布鲁因图中进行神经消息传递能够产生一种因果感知图神经网络架构,用于时间图的建模。基于这些德·布鲁因图神经网络(DBGNN),在今年的 TGL 研讨会上,Heeg 和 Scholtes提出了解决预测节点时间介入度和紧密度中心性的挑战。由于它们受到时间箭头的影响,时间节点的中心性可能与静态中心性大相径庭。而且,计算这些中心性是非常昂贵的!这个问题通过在时间图的第一个时间间隔上训练一个 DBGNN 模型来解决,然后使用训练好的模型预测其余数据中的时间中心性。整体方法如上图所示。实证结果令人鼓舞,展示了因果感知图学习的潜力。我们也希望在 2024 年,社区能更多关注在时间图上学习因果结构的问题。
对因果感知时序图学习感兴趣吗?那么有好消息!上述技术已在开源库 pathpyG 中实现,该库建立在 PyG 之上。这里有一个 入门视频和教程 可供参考。此外,还有一场 在时序图阅读小组中进行的讲座录音,为您提供了对基础研究的深入介绍。
可解释的时序图方法
“现实世界中最重要的图结构数据本质上是时序性的。可解释的时序图模型有潜力揭示长期存在的问题,提供有效的策略和知识,以便用于时序预测,进而从深度学习模型中提取洞察,并辅助科学发现与预测。” — Rex Ying,耶鲁大学助理教授
2023 年首次提出了时序 GNN 方法的可解释性。解释器在高风险应用中非常重要,比如欺诈检测和医疗保健中的疾病进展预测。Xia 等人提出了 T-GNNExplainer,作为首个为时序图模型设计的解释器。T-GNNExplainer 是模型无关的,它从一组候选事件中找到最能解释模型预测的重要事件。Xia 等人将识别解释事件子集的问题视为一个组合优化问题,通过在给定大小的时序图子集内进行搜索来解决。为了解决这个问题,T-GNNExplainer 采用了探索者-导航者框架。导航者通过多个目标事件的训练,捕捉事件间的归纳相关性,而探索者则基于蒙特卡洛树搜索寻找特定的事件组合,包括节点选择、节点扩展、奖励模拟和反向传播。哪些事件被剪枝由导航者推断得出。下图展示了 T-GNNExplainer 的框架。
T-GNNExplainer 框架
图像来源:Xia 等人,2023
最近,Chen 等人认为,要为时序图事件形成人类可理解的解释,解释需要是与预测事件在时间上接近且在空间上相邻的事件,称为紧凑解释。利用时序模式,即时序图中反复出现的子结构,是为时序图形成紧凑解释的自然解决方案。这是因为时序模式是引导未来事件生成过程的重要因素。在以下示例中,优先附着规则(通常在电子商务中促进影响者效应)和三分闭合规则(解释社交网络中的共同朋友规则)形成了紧凑且可信的解释。
连贯的解释在时间上是近似的,并且在空间上是相邻的。
图片来源:Chen et al. 2023
因此,Chen et al.提出了 TempME,一种基于时间模式的解释器,用于识别重要的时间模式以解释时间 GNN。TempME 的框架如下图所示。首先,提取围绕目标预测的时间模式。然后,这些候选模式通过模式编码器进行编码,该编码器利用事件匿名化、信息传递和图池化生成每个模式的嵌入。接着,基于信息瓶颈原则,TempME 为这些模式分配重要性评分,评分由解释准确性和信息压缩共同约束。最后,通过从伯努利分布中采样,基于重要性评分构建解释。
TempME 的框架,边缘上的数字表示时间顺序。
图片致谢:Chen et al. 2023
时间图上的对抗性攻击
“随着时间图被用于重要任务,如欺诈检测,理解其在对抗性攻击下的失败点变得尤为重要。理解和量化这些盲点是创建强健可靠的时间 GNN 模型的第一步。这类努力对于确保这些模型在行业中的应用至关重要。” - 佐治亚理工学院助理教授 Srijan Kumar
对抗性攻击可以针对客户的隐私或影响金融系统中的关键决策。随着时间图模型被应用于推荐系统和欺诈检测等应用领域,研究攻击并设计针对 TG 模型的防御机制变得尤为重要。Chen et al.提出了针对离散时间动态图的第一个对抗性攻击,称为时间感知梯度攻击(TGA)。TGA 通过改变原始网络中的少量链接,且通过梯度信息决定与预测链接最相关的链接。
时间动态感知扰动攻击概述。
攻击者可以在避免检测的情况下翻转模型的预测结果。
图片来源:Sharma et al. 2023
最近,Sharma et al.提出有效的时间图攻击必须同时优化边缘和时间扰动,同时保持原始图演化的完整性。这是因为,剧烈的攻击会扰乱图的演化,容易被异常检测方法发现。因此,Sharma et al. 将演化保持攻击定义为Temporal Dynamics-Aware Perturbation(TDAP)约束。TDAP 主张,在给定时间戳下添加的扰动,只能是相对于前一个时间戳的实际变化数量的一小部分。TDAP 被证明可以保持图的结构和嵌入空间的变化率。下图展示了 TDAP 的概览。Sharma et al. 随后提出了一种新型攻击方法——Temporal Dynamics-aware Projected Gradient Descent(TD-PGD),该方法在 TDAP 约束下具有封闭形式的投影算子。还提出了 TD-PGD 的在线版本,在该版本中,扰动可以实时添加。最后,实验证明,TDAP 约束的扰动确实可以避开基于嵌入的异常检测方法的攻击。
库和基准测试
2023 年,时间图学习领域推动了标准化库和基准测试的重大进展。TGB为节点和链路级任务提供了一个开放且标准化的基准。DyGLib是一个库,包含标准化的训练流程、可扩展的编码接口和全面的时间图学习评估策略。Zhang et al.提出了一个新颖的概念——Live Graph Lab,根据区块链交易提供实时图数据。Live Graph Lab 提供了一套工具,用于下载、解析、清理和分析区块链交易,向研究人员提供随时提取最新时间图数据的机会,以供分析或测试。Zhou et al.注意到,TG 模型中使用的节点内存更适合小批量训练,并且需要在所有训练器中同步维护。因此,他们提出了DistTGL,一种高效且可扩展的解决方案,用于在分布式 GPU 集群上训练基于内存的 TGNN。启用多 GPU 训练大规模数据集是部署 TG 模型在大数据集上的一个重要方向。我们呈现了一个关于时间图学习的库和基准测试的更新列表:
加入时序图学习社区
要加入快速发展的 TG 社区,请点击 此处 注册每周的时序图阅读小组。访问 阅读小组网站 和 Youtube 查看所有即将举办和过去的录播讲座。我们还在网站上提供了 TG slack 的邀请链接(每月更新)。2023 年 NeurIPS 时序图学习研讨会第二版包括了激动人心的 35 篇海报,展示了时序图领域的前沿研究。你还可以在 NeurIPS 虚拟网站 上找到讲座录音(一个月后公开)。如果你想成为下一版研讨会的审稿人,可以 在此 注册。要了解更多关于本文作者的研究,请访问我们的个人网站:沈阳(Andy)黄,Emanuele Rossi,Michael Galkin,Andrea Cini 和 Ingo Scholtes。期待在阅读小组或下次研讨会中见到你!
NeurIPS 2023 时序图学习研讨会的 logo。
图片来自作者。
十个关于 2025 年数据科学与 AI 的预测
关于代理、开源模型、安全性等话题
·发表于Towards Data Science ·阅读时长 14 分钟·6 天前
--
图片由Phil Desforges提供,来源于Unsplash
在年末的一场 AI 大会上,我正在演讲者休息室整理工作,当时三位声音很大的 AI 高管进入,正好赶上当天的倒数第二个小组讨论,主题是“AI 的未来”。在迅速扫视我一眼(可能是确保我只是个无害的 NPC)后,其中一位大声宣布:“这应该是我今年的第 30 次?35 次?参加的大会了。”
经过一番停顿后,他补充道:“…你知道吗,他们现在开始听起来都差不多了。”
当我在思考如何在耳膜里安装护栏来过滤掉自夸的言辞时,我不得不承认:他说得有道理。在 AI 的叙事中存在令人不安的“相似性”。听起来像是:
AI 代理和代理工作流是下一个浪潮。
AI 飞行员很多,生产中的 AI 却充满风险。
AI 不会抢走你的工作,但懂 AI 的人会。
AI 治理很重要。欧盟 AI 法案什么的。
随着我们跨入 2025 年,尽管每年发布超过24 万篇 AI 研究论文,并且面临可重复性危机,我不禁想知道,这些论文中有多少真的是突破性的,而不是在追逐下一个微小的进步,重复着另一种未标准化的……
TensorFlow Transform:确保生产中的数据准备无缝进行
利用 TensorFlow Transform 来扩展生产环境中的数据管道
·发表于Towards Data Science ·阅读时长 10 分钟·2024 年 7 月 8 日
--
图片来自Suzanne D. Williams 在Unsplash
数据预处理是任何机器学习管道中的主要步骤之一。TensorFlow Transform 帮助我们在分布式环境中处理庞大的数据集。
在深入探讨数据转换之前,数据验证是生产管道过程的第一步,我在我的文章生产管道中的数据验证:TFX 方式中已经讲解过。请阅读这篇文章,以便更好地理解本文内容。
我在这个演示中使用了 Colab,因为它配置环境更简单(且更快)。如果你正处于探索阶段,我也建议使用 Colab,因为它能帮助你专注于更重要的内容。
机器学习管道操作从数据摄取和验证开始,然后是数据转换。转换后的数据进行训练并部署。我在之前的文章中已经讲解了验证部分,现在我们将讨论数据转换部分。为了更好地理解 TensorFlow 中的管道,请查看下面的文章。
[## TFX | 机器学习生产管道 | TensorFlow
构建和管理端到端的生产级机器学习管道。TFX 组件支持可扩展的、高性能的数据处理……
如前所述,我们将使用 Colab。所以我们只需要安装 tfx 库,就可以开始了。
! pip install tfx
安装完成后,重启会话以继续。
接下来是导入部分。
# Importing Libraries
import tensorflow as tf
from tfx.components import CsvExampleGen
from tfx.components import ExampleValidator
from tfx.components import SchemaGen
from tfx.v1.components import ImportSchemaGen
from tfx.components import StatisticsGen
from tfx.components import Transform
from tfx.orchestration.experimental.interactive.interactive_context import InteractiveContext
from google.protobuf.json_format import MessageToDict
import os
我们将使用来自 Kaggle 的太空船泰坦尼克数据集,如数据验证文章中所示。该数据集可以用于商业和非商业目的,且免费使用。你可以从这里访问它。数据集的描述见下图。
为了开始数据转换部分,建议创建文件夹来存放管道组件(否则它们将被放置在默认目录中)。我创建了两个文件夹,一个用于管道组件,另一个用于我们的训练数据。
# Path to pipeline folder
# All the generated components will be stored here
_pipeline_root = '/content/tfx/pipeline/'
# Path to training data
# It can even contain multiple training data files
_data_root = '/content/tfx/data/'
接下来,我们创建 InteractiveContext,并传递管道目录的路径。此过程还会创建一个 sqlite 数据库,用于存储管道过程的元数据。
InteractiveContext 用于探索流程的每个阶段。在每个阶段,我们可以查看所创建的工件。在生产环境中,我们理想的做法是使用像 Apache Beam 这样的管道创建框架,在该框架中,整个过程会自动执行,无需人工干预。
# Initializing the InteractiveContext
# This will create an sqlite db for storing the metadata
context = InteractiveContext(pipeline_root=_pipeline_root)
接下来,我们开始数据导入。如果你的数据存储为 csv 文件,我们可以使用 CsvExampleGen,并传递数据文件所在目录的路径。
确保文件夹中只包含训练数据,且没有其他内容。如果你的训练数据分为多个文件,请确保它们具有相同的标题。
# Input CSV files
example_gen = CsvExampleGen(input_base=_data_root)
TFX 当前支持 csv、tf.Record、BigQuery 和一些自定义执行器。更多信息请参见以下链接。
[## ExampleGen TFX 管道组件 | TensorFlow
ExampleGen TFX 管道组件将数据导入 TFX 管道。它使用外部文件/服务生成……
要执行 ExampleGen 组件,使用 context.run。
# Execute the component
context.run(example_gen)
运行组件后,这将是我们的输出。它提供了执行 ID、组件详情以及组件输出保存的位置。
展开后,我们应该能够看到这些详情。
目录结构如下图所示。所有这些工件都是由 TFX 为我们创建的,它们也会自动进行版本控制,详细信息存储在 metadata.sqlite 中。sqlite 文件有助于维护数据来源或数据血统。
要以编程方式探索这些工件,可以使用以下代码。
# View the generated artifacts
artifact = example_gen.outputs['examples'].get()[0]
# Display split names and uri
print(f'split names: {artifact.split_names}')
print(f'artifact uri: {artifact.uri}')
输出将是文件名和 uri。
让我们复制训练的 uri,并查看文件中的详细信息。该文件以 zip 格式存储,并以 TFRecordDataset 格式存储。
# Get the URI of the output artifact representing the training examples
train_uri = os.path.join(artifact.uri, 'Split-train')
# Get the list of files in this directory (all compressed TFRecord files)
tfrecord_filenames = [os.path.join(train_uri, name)
for name in os.listdir(train_uri)]
# Create a `TFRecordDataset` to read these files
dataset = tf.data.TFRecordDataset(tfrecord_filenames, compression_type="GZIP")
以下代码来自 Tensorflow,这是一个标准代码,可以用来从 TFRecordDataset 中获取记录,并返回供我们检查的结果。
# Helper function to get individual examples
def get_records(dataset, num_records):
'''Extracts records from the given dataset.
Args:
dataset (TFRecordDataset): dataset saved by ExampleGen
num_records (int): number of records to preview
'''
# initialize an empty list
records = []
# Use the `take()` method to specify how many records to get
for tfrecord in dataset.take(num_records):
# Get the numpy property of the tensor
serialized_example = tfrecord.numpy()
# Initialize a `tf.train.Example()` to read the serialized data
example = tf.train.Example()
# Read the example data (output is a protocol buffer message)
example.ParseFromString(serialized_example)
# convert the protocol bufffer message to a Python dictionary
example_dict = (MessageToDict(example))
# append to the records list
records.append(example_dict)
return records
# Get 3 records from the dataset
sample_records = get_records(dataset, 3)
# Print the output
pp.pprint(sample_records)
我们请求了 3 条记录,输出如下。每条记录及其元数据都以字典格式存储。
接下来,我们进入下一个步骤,即使用 StatisticsGen 生成数据的统计信息。我们将 example_gen 对象的输出作为参数传递。
我们使用 statistics.run 来执行组件,并将 statistics_gen 作为参数。
# Generate dataset statistics with StatisticsGen using the example_gen object
statistics_gen = StatisticsGen(
examples=example_gen.outputs['examples'])
# Execute the component
context.run(statistics_gen)
我们可以使用 context.show 来查看结果。
# Show the output statistics
context.show(statistics_gen.outputs['statistics'])
你可以看到,这与我们在 TFDV 文章中讨论的统计生成非常相似。原因是,TFX 在底层使用 TFDV 来执行这些操作。熟悉 TFDV 有助于更好地理解这些过程。
下一步是创建 schema。这是通过使用 SchemaGen 并传递 statistics_gen 对象来完成的。运行组件并使用 context.show 来可视化它。
# Generate schema using SchemaGen with the statistics_gen object
schema_gen = SchemaGen(
statistics=statistics_gen.outputs['statistics'],
)
# Run the component
context.run(schema_gen)
# Visualize the schema
context.show(schema_gen.outputs['schema'])
输出显示了数据底层模式的详细信息。同样,这与 TFDV 中的内容类似。
如果需要修改这里提供的 schema,请使用 tfdv 进行修改,并创建一个 schema 文件。你可以通过 ImportSchemaGen 传递它,并要求 tfx 使用新文件。
# Adding a schema file manually
schema_gen = ImportSchemaGen(schema_file="path_to_schema_file/schema.pbtxt")
接下来,我们使用 ExampleValidator 来验证示例。我们将 statistics_gen 和 schema_gen 作为参数传递。
# Validate the examples using the ExampleValidator
# Pass statistics_gen and schema_gen objects
example_validator = ExampleValidator(
statistics=statistics_gen.outputs['statistics'],
schema=schema_gen.outputs['schema'])
# Run the component.
context.run(example_validator)
这应该是你的理想输出,以显示一切正常。
在这一点上,我们的目录结构如下图所示。我们可以看到,在每个过程步骤中,都会创建相应的工件。
让我们进入实际的转换部分。我们现在将创建 constants.py 文件,以添加处理过程中所需的所有常量。
# Creating the file containing all constants that are to be used for this project
_constants_module_file = 'constants.py'
我们将创建所有常量并写入 constants.py 文件。请看“%%writefile {_constants_module_file}”,此命令并不会让代码运行,而是将给定单元格中的所有代码写入指定的文件。
%%writefile {_constants_module_file}
# Features with string data types that will be converted to indices
CATEGORICAL_FEATURE_KEYS = [ 'CryoSleep','Destination','HomePlanet','VIP']
# Numerical features that are marked as continuous
NUMERIC_FEATURE_KEYS = ['Age','FoodCourt','RoomService', 'ShoppingMall','Spa','VRDeck']
# Feature that can be grouped into buckets
BUCKET_FEATURE_KEYS = ['Age']
# Number of buckets used by tf.transform for encoding each bucket feature.
FEATURE_BUCKET_COUNT = {'Age': 4}
# Feature that the model will predict
LABEL_KEY = 'Transported'
# Utility function for renaming the feature
def transformed_name(key):
return key + '_xf'
让我们创建 transform.py 文件,其中将包含用于转换数据的实际代码。
# Creating a file that contains all preprocessing code for the project
_transform_module_file = 'transform.py'
在这里,我们将使用 tensorflow_transform 库。变换过程的代码将在 preprocessing_fn 函数下编写。我们必须使用相同的名称,因为 tfx 在变换过程中会在内部查找它。
%%writefile {_transform_module_file}
import tensorflow as tf
import tensorflow_transform as tft
import constants
# Unpack the contents of the constants module
_NUMERIC_FEATURE_KEYS = constants.NUMERIC_FEATURE_KEYS
_CATEGORICAL_FEATURE_KEYS = constants.CATEGORICAL_FEATURE_KEYS
_BUCKET_FEATURE_KEYS = constants.BUCKET_FEATURE_KEYS
_FEATURE_BUCKET_COUNT = constants.FEATURE_BUCKET_COUNT
_LABEL_KEY = constants.LABEL_KEY
_transformed_name = constants.transformed_name
# Define the transformations
def preprocessing_fn(inputs):
outputs = {}
# Scale these features to the range [0,1]
for key in _NUMERIC_FEATURE_KEYS:
outputs[_transformed_name(key)] = tft.scale_to_0_1(
inputs[key])
# Bucketize these features
for key in _BUCKET_FEATURE_KEYS:
outputs[_transformed_name(key)] = tft.bucketize(
inputs[key], _FEATURE_BUCKET_COUNT[key])
# Convert strings to indices in a vocabulary
for key in _CATEGORICAL_FEATURE_KEYS:
outputs[_transformed_name(key)] = tft.compute_and_apply_vocabulary(inputs[key])
# Convert the label strings to an index
outputs[_transformed_name(_LABEL_KEY)] = tft.compute_and_apply_vocabulary(inputs[_LABEL_KEY])
return outputs
我们在这个演示中使用了一些标准的缩放和编码函数。实际上,变换库包含了许多函数,可以在这里探索它们。
TF.Transform 的初始化模块。
www.tensorflow.org](https://www.tensorflow.org/tfx/transform/api_docs/python/tft?source=post_page-----99ffcf49f535--------------------------------)
现在是时候查看变换过程的实际操作了。我们创建一个 Transform 对象,并传入 example_gen 和 schema_gen 对象,以及我们创建的 transform.py 文件的路径。
# Ignore TF warning messages
tf.get_logger().setLevel('ERROR')
# Instantiate the Transform component with example_gen and schema_gen objects
# Pass the path for transform file
transform = Transform(
examples=example_gen.outputs['examples'],
schema=schema_gen.outputs['schema'],
module_file=os.path.abspath(_transform_module_file))
# Run the component
context.run(transform)
运行它,变换部分就完成了!
看一下下面图像中显示的变换数据。
为什么不直接使用 scikit-learn 库或 pandas 来做这些?
这就是你现在的问题,对吧?
这个过程并不适用于想要预处理数据并开始训练模型的个人用户。它是为大规模数据(需要分布式处理的数据)和无法中断的自动化生产管道而设计的。
应用变换后,你的文件夹结构如下所示:
它包含了变换前后的详细信息。此外,还创建了一个变换图。
请记住,我们使用 tft.scale_to_0_1 对数值特征进行了缩放。像这样的函数需要计算一些细节,这些细节需要分析整个数据(例如特征的均值、最小值和最大值)。分析分布在多台机器上的数据以获取这些细节是性能密集型的(特别是如果多次执行时)。这些细节只会计算一次,并保存在 transform_graph 中。任何时候一个函数需要它们时,它都会直接从 transform_graph 中获取。这还有助于将训练阶段创建的变换直接应用于服务数据,确保预处理阶段的一致性。
使用 Tensorflow Transform 库的另一个主要优势是,每个阶段都会被记录为工件,从而保持数据的血缘关系。当数据发生变化时,数据版本控制也会自动进行。因此,它使得在生产环境中进行实验、部署和回滚变得更加容易。
就是这样。如果你有任何问题,请在评论区写下来。
你可以从我的 GitHub 仓库下载本文章中使用的笔记本和数据文件,点击这个链接。
接下来做什么?
若想更好地理解管道组件,请阅读以下文章。
MLOps 是将 DevOps 实践应用于机器学习(ML)工作流的实践,旨在帮助自动化、管理和审计机器学习工作流…
感谢阅读我的文章。如果你喜欢它,请通过给我一些掌声来鼓励我;如果你有不同的看法,欢迎在评论中告诉我有哪些可以改进的地方。再见。
除非另有说明,所有图片均由作者提供。
Terraform 配置 Dataform
MLOps:数据管道编排
Dataform 101,第二部分:使用最小权限访问控制进行配置
·发表于Towards Data Science ·阅读时间:7 分钟·2024 年 5 月 31 日
--
Dataform 在数据管道中的典型定位 [作者提供的图片]
这是 Dataform 101 的结尾部分,展示了设置 Dataform 的基本原理,重点讲解其身份验证流程。第二部分重点讲解了在第一部分中解释的流程的 Terraform 实现。
Dataform 配置
可以通过 GCP 控制台设置 Dataform,但 Terraform 提供了一种优雅的方式来配置和管理诸如 Dataform 之类的基础设施。使用 Terraform 提供了可移植性、可重用性和基础设施版本控制等诸多好处。因此,在本节中,您需要具备 Terraform 知识。如果您熟悉 Terraform,请前往GitHub 仓库并下载所有代码。如果不熟悉,可以参考 Google Cloud Skills Boost 提供的资源来入门。
单一仓库、多环境的 Dataform 架构流程
环境设置
我们首先设置prod
和staging
两个环境,如上面的架构流程图所示。需要注意的是,代码开发是在 macOS 系统上进行的,因此,Windows 系统的用户可能需要做一些调整才能跟随操作。
mkdir prod
mkdir staging
设置临时文件
所有初始代码都写在staging
目录中。这是因为所提议的架构在临时环境中配置了 Dataform,而在生产环境中仅配置了少量资源。
让我们开始配置一个远程存储桶,用于在远程后端存储 Terraform 状态。这一步将手动完成,我们不会将存储桶纳入 Terraform 管理。这有点像“先有鸡还是先有蛋”的问题,即存储 Terraform 状态的存储桶是否应由同一 Terraform 管理。可以称其为一种进退两难的局面。因此,我们将在暂存环境中手动创建一个名为dataform-staging-terraform-state的存储桶,并在staging
目录中添加以下内容:
#staging/backend.tf
terraform {
backend "gcs" {
bucket = "dataform-staging-terraform-state"
prefix = "terraform/state"
}
接下来,向代码库中添加资源提供者。
#staging/providers.tf
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = ">=5.14.0"
}
google-beta = {
source = "hashicorp/google-beta"
version = ">=5.14.0"
}
}
required_version = ">= 1.7.3"
}
provider "google" {
project = var.project_id
}
然后我们创建一个变量文件,定义所有用于基础设施配置的变量。
#staging/variables.tf
variable "project_id" {
type = string
description = "Name of the GCP Project."
}
variable "region" {
type = string
description = "The google cloud region to use"
default = "europe-west2"
}
variable "project_number" {
type = string
description = "Number of the GCP Project."
}
variable "location" {
type = string
description = "The google cloud location in which to create resources"
default = "EU"
}
variable "dataform_staging_service_account" {
type = string
description = "Email of the service account Dataform uses to execute queries in staging env"
}
variable "dataform_prod_service_account" {
type = string
description = "Email of the service account Dataform uses to execute queries in production"
}
variable "dataform_github_token" {
description = "Dataform GitHub Token"
type = string
sensitive = true
}
auto.tfvars
文件被添加,以确保变量能够自动发现。确保适当替换文件中的变量占位符。
#staging/staging.auto.tfvars
project_id = "{staging-project-id}"
region = "{staging-project--region}"
project_number = "{staging-project-number}"
dataform_staging_service_account = "dataform-staging"
dataform_prod_service_account = "{dataform-prod-service-account-email}"
dataform_github_token = "dataform_github_token"
接下来是密钥配置阶段,其中存储了机器用户的令牌。
#staging/secrets.tf
resource "google_secret_manager_secret" "dataform_github_token" {
project = var.project_id
secret_id = var.dataform_github_token
replication {
user_managed {
replicas {
location = var.region
}
}
}
}
配置完密钥后,data
资源将被添加到 terraform 代码库中,用于动态读取存储的密钥值,以便在配置完成后,Dataform 能够访问机器用户的 GitHub 凭证。data
资源依赖于密钥资源,确保只有在密钥已经配置好之后才会执行。
#staging/data.tf
data "google_secret_manager_secret_version" "dataform_github_token" {
project = var.project_id
secret = var.dataform_github_token
depends_on = [
google_secret_manager_secret.dataform_github_token
]
}
我们继续配置暂存环境所需的服务账户,并授予所需的权限以将数据展现到 BigQuery。
#staging/service_accounts.tf
resource "google_service_account" "dataform_staging" {
account_id = var.dataform_staging_service_account
display_name = "Dataform Service Account"
project = var.project_id
}
还有 BQ 权限。
#staging/iam.tf
resource "google_project_iam_member" "dataform_staging_roles" {
for_each = toset([
"roles/bigquery.dataEditor",
"roles/bigquery.dataViewer",
"roles/bigquery.user",
"roles/bigquery.dataOwner",
])
project = var.project_id
role = each.value
member = "serviceAccount:${google_service_account.dataform_staging.email}"
depends_on = [
google_service_account.dataform_staging
]
}
现在是关键时刻,因为我们已经具备了在暂存环境中配置 Dataform 所需的所有基础设施。
#staging/dataform.tf
resource "google_dataform_repository" "dataform_demo" {
provider = google-beta
name = "dataform_demo"
project = var.project_id
region = var.region
service_account = "${var.dataform_staging_service_account}@${var.project_id}.iam.gserviceaccount.com"
git_remote_settings {
url = "https://github.com/kbakande/terraforming-dataform"
default_branch = "main"
authentication_token_secret_version = data.google_secret_manager_secret_version.dataform_github_token.id
}
workspace_compilation_overrides {
default_database = var.project_id
}
}
resource "google_dataform_repository_release_config" "prod_release" {
provider = google-beta
project = var.project_id
region = var.region
repository = google_dataform_repository.dataform_demo.name
name = "prod"
git_commitish = "main"
cron_schedule = "30 6 * * *"
code_compilation_config {
default_database = var.project_id
default_location = var.location
default_schema = "dataform"
assertion_schema = "dataform_assertions"
}
depends_on = [
google_dataform_repository.dataform_demo
]
}
resource "google_dataform_repository_workflow_config" "prod_schedule" {
provider = google-beta
project = var.project_id
region = var.region
name = "prod_daily_schedule"
repository = google_dataform_repository.dataform_demo.name
release_config = google_dataform_repository_release_config.prod_release.id
cron_schedule = "45 6 * * *"
invocation_config {
included_tags = []
transitive_dependencies_included = false
transitive_dependents_included = false
fully_refresh_incremental_tables_enabled = false
service_account = var.dataform_prod_service_account
}
depends_on = [
google_dataform_repository.dataform_demo
]
}
google_dataform_repository资源配置一个 Dataform 仓库,指定目标远程仓库以及访问该仓库的令牌。接着我们配置发布设置,说明从哪个远程仓库分支生成编译,并配置定时任务的时间。
最后,工作流配置也将配置好,安排稍微滞后于发布配置,以确保在工作流配置运行时,最新的编译版本已经可用。
一旦 Dataform 配置完成,一个默认的服务账户将与其一同创建,格式为service-{project_number}@gcp-sa-dataform.iam.gserviceaccount.com。这个默认服务账户需要模拟暂存和生产环境中的服务账户,以便在这些环境中实现数据。
我们在暂存环境中修改iam.tf
文件,以授予 Dataform 默认服务账户所需的角色,使其能够模拟暂存环境中的服务账户并访问已配置的密钥。
#staging/iam.tf
resource "google_project_iam_member" "dataform_staging_roles" {
for_each = toset([
"roles/bigquery.dataEditor",
"roles/bigquery.dataViewer",
"roles/bigquery.user",
"roles/bigquery.dataOwner",
])
project = var.project_id
role = each.value
member = "serviceAccount:${google_service_account.dataform_staging.email}"
depends_on = [
google_service_account.dataform_staging
]
}
resource "google_service_account_iam_binding" "custom_service_account_token_creator" {
service_account_id = "projects/${var.project_id}/serviceAccounts/${var.dataform_staging_service_account}@${var.project_id}.iam.gserviceaccount.com"
role = "roles/iam.serviceAccountTokenCreator"
members = [
"serviceAccount:@gcp-sa-dataform.iam.gserviceaccount.com">service-${var.project_number}@gcp-sa-dataform.iam.gserviceaccount.com"
]
depends_on = [
module.service-accounts
]
}
resource "google_secret_manager_secret_iam_binding" "github_secret_accessor" {
secret_id = google_secret_manager_secret.dataform_github_token.secret_id
role = "roles/secretmanager.secretAccessor"
members = [
"serviceAccount:@gcp-sa-dataform.iam.gserviceaccount.com">service-${var.project_number}@gcp-sa-dataform.iam.gserviceaccount.com"
]
depends_on = [
google_secret_manager_secret.dataform_github_token,
module.service-accounts,
]
}
基于最小权限访问控制原则,针对目标资源的 IAM 绑定用于授予默认服务账户精细化访问权限。
为了不让这篇文章过长,生产环境中资源配置的 Terraform 代码可以在GitHub 仓库中找到。我们只需要在生产环境中配置远程后端存储桶和服务账户(以及默认服务账户的细粒度权限)。如果配置成功,暂存环境中的 Dataform 状态应类似于下面的图片。
GCP 中成功配置后的 Dataform 状态
以下是所提架构的一些优缺点:
优点
-
遵循版本控制的原则。所提架构只有一个版本,但代码可以在多个环境中实现。
-
实验仅限于暂存环境,这可以减少对生产数据的意外修改的可能性。
缺点
-
担心默认服务账户可能会在生产环境中做出意外更改,但通过最小权限访问控制可以缓解这个问题。
-
多个开发人员在暂存环境中并行工作时,可能会覆盖数据。尽管在本文中没有显示,但这个问题可以通过 Dataform 的工作区编译覆盖和模式后缀功能来缓解。
像任何架构一样,都会有优缺点。最终的决策应基于组织内部的具体情况。希望这篇文章能够帮助做出这个决策。
总结
在第一部分中,我们介绍了在 GCP Dataform 中使用的一些术语,并演示了单个仓库、多环境 Dataform 设置的身份验证流程。本部分 2 提供了 Terraform 代码,并介绍了如何为服务账户实施最小权限访问控制的方法。
希望这篇文章能帮助你更好地理解 Dataform。我们可以在LinkedIn上联系。
图片来源:本文中的所有图片均由作者创建
参考文献
实践中的测试:代码、数据与 ML 模型
MLOps 中的测试指南
·发表于Towards Data Science ·12 分钟阅读·2024 年 1 月 12 日
--
如果你想要一个正确且可靠的机器学习模型,且具备良好的性能,那么测试是必须进行的基本实践之一。如果你决定深入了解测试方法,那么你来对地方了。在本文中,我们通过一个实际案例来解释测试的重要性,其中我们将测试应用于机器学习工作流的不同步骤。本文的完整代码库可以在相关的仓库中访问。
还不是 Medium 会员?不用担心!通过这个好友链接继续阅读。
目录:
· 1. 介绍
· 2. 项目设置
· 3. 代码测试
∘ 3.1. 单元测试
∘ 3.2. 集成测试
· 4. 数据测试
∘ 4.1. 数据验证
∘ 4.2. 符合政策要求
∘ 4.3. 特征重要性
· 5. 模型测试
· 6. 结论
1. 介绍
测试 Unitree Go-1 的现场能力
宣传视频很棒,但把一只机器人狗带到现场实际情况如何?
·发表于Towards Data Science ·阅读时间 8 分钟·2024 年 7 月 6 日
--
我正在与我的儿子 Arthur 一起度过夏天,感谢Rady 计算机科学与工程学院的奖学金,以及科罗拉多大学的合作伙伴洛基山生物实验室(RMBL)。我们还带来了一只来自科罗拉多大学博尔德分校合作 AI 与机器人实验室的Unitree Go-1。RMBL 的科学家不仅是生物学家,很多人还成为了无人机操作员、传感器工程师和数据科学家。我们的目标是了解一只商品化的机器人狗能为他们的工具集合带来什么,它在现场能够完成哪些任务,以及机器人学的哪些基础研究是实现这些任务所需的。
如果您不是 Medium 订阅者,您可以 在此处. 免费阅读此文章。
Unitree Go1 正在科罗拉多州哥特镇的洛基山生物实验室(Rocky Mountain Biological Lab)穿越碎石场。原创作品。
以下是我们第一次部署的主要发现:
-
Unitree Go-1 能够在非常崎岖的地形上导航。
-
机器人确实会失败。它的腿部可能会与草本植物和灌木的茎部缠绕,甚至在平坦的地形上(!)机器人也很容易滑倒。
-
如果机器人失败,它通常无法自行恢复,需要手动解开缠绕并重新启动。
文本嵌入、分类和语义搜索
带有示例 Python 代码的介绍
·发布于 Towards Data Science ·11 分钟阅读·2024 年 3 月 27 日
--
本文是关于实践中使用大型语言模型(LLMs)的一部分 系列文章。在 上一篇文章,我们看到如何通过检索增强生成(即 RAG)来改进 LLM。RAG 的一个关键部分是使用文本嵌入从知识库中自动检索相关信息。在这里,我将更深入地讨论文本嵌入,并分享两个简单(但强大的)应用:文本分类和语义搜索。
图片由 Daniel Lerman 提供,来源于 Unsplash
ChatGPT 激发了全球对人工智能及其潜力的想象力。其重要推动力之一是 ChatGPT 的聊天界面,使得人工智能的强大功能变得前所未有地易于接触。
尽管这开启了人工智能热潮和认知的新层次,但围绕“聊天机器人范式”的所有兴奋却使得另一个关键创新(在很大程度上)被忽视。
LLMs 带来了 文本嵌入领域的重大创新。在这里,我将解释这些创新以及如何利用它们来应对简单但高价值的用例。
文本嵌入:全面指南
嵌入的演变、可视化和应用
·发表于Towards Data Science ·阅读时间:20 分钟·2024 年 2 月 13 日
--
图片由 DALL-E 3 生成
作为人类,我们可以阅读和理解文本(至少是其中一些)。相反,计算机“用数字思考”,因此它们无法自动理解单词和句子的含义。如果我们希望计算机理解自然语言,我们需要将这些信息转换为计算机可以处理的格式——数字向量。
许多年前,人们就学会了如何将文本转换为计算机可以理解的格式(最早的版本之一是ASCII)。这种方法有助于渲染和传输文本,但并没有编码单词的含义。当时,标准的搜索技术是关键字搜索,即仅仅查找包含特定单词或 N-gram 的所有文档。
随后,经过数十年的发展,嵌入出现了。我们可以计算单词、句子甚至图像的嵌入。嵌入也是数字向量,但它们可以捕捉到意义。因此,你可以用它们进行语义搜索,甚至处理不同语言的文档。
在本文中,我将深入探讨嵌入的主题,并讨论所有细节:
-
嵌入之前发生了什么,以及它们是如何演变的,
-
如何使用 OpenAI 工具计算嵌入,
-
如何判断句子之间的相似度,
-
如何可视化嵌入,
-
最令人兴奋的部分是你如何在实践中使用嵌入。
让我们继续前进,了解嵌入的演变。
嵌入的演变
我们将从简要回顾文本表示的历史开始我们的旅程。
词袋模型
将文本转换成向量的最基本方法是词袋模型。让我们看看理查德·费曼的名言之一“我们很幸运生活在一个仍在不断发现的时代”。我们将用它来说明词袋模型的方法。
获取词袋向量的第一步是将文本拆分为单词(词元),然后将单词简化为其基础形式。例如,“running”将转换为“run”。这个过程叫做词干提取。我们可以使用 NLTK Python 包来完成这一操作。
from nltk.stem import SnowballStemmer
from nltk.tokenize import word_tokenize
text = 'We are lucky to live in an age in which we are still making discoveries'
# tokenization - splitting text into words
words = word_tokenize(text)
print(words)
# ['We', 'are', 'lucky', 'to', 'live', 'in', 'an', 'age', 'in', 'which',
# 'we', 'are', 'still', 'making', 'discoveries']
stemmer = SnowballStemmer(language = "english")
stemmed_words = list(map(lambda x: stemmer.stem(x), words))
print(stemmed_words)
# ['we', 'are', 'lucki', 'to', 'live', 'in', 'an', 'age', 'in', 'which',
# 'we', 'are', 'still', 'make', 'discoveri']
现在,我们有了所有单词的基础形式列表。下一步是计算它们的频率以创建向量。
import collections
bag_of_words = collections.Counter(stemmed_words)
print(bag_of_words)
# {'we': 2, 'are': 2, 'in': 2, 'lucki': 1, 'to': 1, 'live': 1,
# 'an': 1, 'age': 1, 'which': 1, 'still': 1, 'make': 1, 'discoveri': 1}
实际上,如果我们想要将文本转换成向量,我们不仅需要考虑文本中的单词,还需要考虑整个词汇表。假设我们的词汇表中也包含“i”、“you”和“study”,那么我们就可以从费曼的名言中创建一个向量。
作者提供的图表
这种方法相当基础,并且没有考虑到单词的语义含义,因此句子“the girl is studying data science”和“the young woman is learning AI and ML”将不会彼此接近。
TF-IDF
词袋模型方法的一个略微改进版本是TF-IDF(词频-逆文档频率)。它是两个指标的乘积。
- 词频表示单词在文档中的出现频率。最常见的计算方法是将单词在文档中的原始计数(就像在词袋模型中一样)除以文档中单词的总数。然而,也有许多其他方法,如仅使用原始计数、布尔“频率”,以及不同的归一化方法。你可以在维基百科上了解更多关于不同方法的信息。
- 逆文档频率表示单词提供的信息量。例如,像“a”或“that”这样的单词不会给你关于文档主题的额外信息。相反,像“ChatGPT”或“生物信息学”这样的单词可以帮助你定义领域(但对于这个句子来说不适用)。它是通过计算包含该单词的文档数量与文档总数的比率的对数来得出的。IDF 值越接近 0,表示该单词越常见,提供的信息越少。
所以,最终我们会得到向量,其中常见单词(如“I”或“you”)的权重较低,而在文档中多次出现的稀有单词的权重较高。这种策略可以提供稍微更好的结果,但它仍然无法捕捉到语义上的含义。
这种方法的另一个挑战是它生成的向量非常稀疏。向量的长度等于语料库的大小。英语中大约有 47 万个独特的单词(来源),因此我们将得到非常大的向量。由于一句话中不会超过 50 个独特单词,所以向量中 99.99% 的值将是 0,无法编码任何信息。基于此,科学家们开始考虑稠密向量表示。
Word2Vec
最著名的稠密表示方法之一是 word2vec,由 Google 在 2013 年通过 Mikolov 等人的论文 《Efficient Estimation of Word Representations in Vector Space》 提出。
论文中提到了两种不同的 word2vec 方法:连续词袋模型(当我们基于周围的词汇预测目标词时)和 Skip-gram(相反的任务——当我们基于词汇预测上下文时)。
论文中的图示,Mikolov 等人,2013 | 来源
稠密向量表示的高级概念是训练两个模型:编码器和解码器。例如,在 Skip-gram 的情况下,我们可能会将词汇 “christmas” 传入编码器。然后,编码器会生成一个向量,我们将这个向量传递给解码器,期望得到词汇 “merry”、“to” 和 “you”。
作者的示意图
该模型开始考虑词汇的含义,因为它是基于词汇的上下文进行训练的。然而,它忽略了词形变化(例如,“-less” 表示缺少某物)。这个缺点后来通过关注子词 Skip-grams 在 GloVe 中得到了改进。
此外,word2vec 只能处理单个词汇,但我们希望对整个句子进行编码。那么,让我们进入下一个进化步骤——使用 transformers。
Transformers 和句子嵌入
下一步的进化涉及到 transformers 方法,这是 Vaswani 等人在《Attention Is All You Need》论文中提出的。Transformers 能够生成信息丰富的稠密向量,并成为现代语言模型的主流技术。
我不会详细介绍 transformers 的架构,因为这与我们的主题关系不大,且会占用大量时间。如果你有兴趣了解更多,有许多关于 transformers 的资料可供参考,例如,《Transformers, Explained》 或 《The Illustrated Transformer》。
Transformers 允许你使用相同的“核心”模型,并针对不同的使用场景进行微调,而无需重新训练核心模型(这需要大量时间且成本高昂)。它推动了预训练模型的兴起。其中一个最早流行的模型是 Google AI 的 BERT(双向编码器表示模型)。
从内部来看,BERT 仍然在与 word2vec 类似的单词级别进行操作,但我们仍然想要获取句子嵌入。所以,最简单的方法是取所有标记(token)向量的平均值。不幸的是,这种方法表现不好。
这个问题在 2019 年得到了解决,当时Sentence-BERT发布。它超越了所有先前的语义文本相似性任务方法,并且能够计算句子嵌入。
这是一个庞大的话题,因此我们无法在这篇文章中完全覆盖它。如果你真的感兴趣,可以在这篇文章中了解更多关于句子嵌入的内容。
我们简要地回顾了嵌入的演变,并对其理论有了一个高层次的理解。现在,是时候进入实践部分,学习如何使用 OpenAI 工具计算嵌入了。
计算嵌入
在这篇文章中,我们将使用 OpenAI 的嵌入模型。我们将尝试一个新的模型 text-embedding-3-small
,它最近被发布。与 text-embedding-ada-002
相比,新模型的表现更好:
-
在一个广泛使用的多语言检索(MIRACL)基准测试中,平均分数从 31.4%上升到了 44.0%。
-
在一个常用的基准测试中,针对英文任务的平均性能(MTEB)也有所提升,从 61.0%提高到了 62.3%。
OpenAI 还发布了一个新的更大模型 text-embedding-3-large
。现在,这是他们表现最好的嵌入模型。
作为数据源,我们将使用一个小样本的Stack Exchange 数据库——这是一个匿名的所有用户贡献内容的转储,来自Stack Exchange 网络。我选择了一些看起来有趣的主题,并从每个主题中随机抽取了 100 个问题。主题从生成性人工智能到咖啡或自行车,涉及内容非常广泛。
首先,我们需要计算所有 Stack Exchange 问题的嵌入。值得做一次并将结果本地存储(在文件或向量存储中)。我们可以使用 OpenAI Python 包来生成嵌入。
from openai import OpenAI
client = OpenAI()
def get_embedding(text, model="text-embedding-3-small"):
text = text.replace("\n", " ")
return client.embeddings.create(input = [text], model=model)\
.data[0].embedding
get_embedding("We are lucky to live in an age in which we are still making discoveries.")
结果,我们得到了一个 1536 维的浮动数值向量。现在,我们可以对所有数据重复这个过程并开始分析这些值。
你可能会问的主要问题是:句子在语义上有多接近?为了揭示答案,让我们讨论一下向量之间的距离概念。
向量之间的距离
嵌入实际上就是向量。因此,如果我们想了解两句话之间的相似度,可以计算它们的向量之间的距离。较小的距离意味着语义上更接近。
可以使用不同的度量方法来衡量两个向量之间的距离:
-
欧几里得距离(L2),
-
曼哈顿距离(L1),
-
点积,
-
余弦距离。
让我们讨论它们。作为一个简单的例子,我们将使用两个二维向量。
vector1 = [1, 4]
vector2 = [2, 2]
欧几里得距离(L2)
定义两个点(或向量)之间距离的最标准方法是欧几里得距离或 L2 范数。这种度量方法在日常生活中最为常见,例如当我们谈论两个城镇之间的距离时。
这里是 L2 距离的可视化表示和公式。
图片来自作者
我们可以使用原生 Python 或利用 numpy 函数来计算这个度量。
import numpy as np
sum(list(map(lambda x, y: (x - y) ** 2, vector1, vector2))) ** 0.5
# 2.2361
np.linalg.norm((np.array(vector1) - np.array(vector2)), ord = 2)
# 2.2361
曼哈顿距离(L1)
另一个常用的距离度量是 L1 范数或曼哈顿距离。这个距离是以曼哈顿岛(纽约)命名的。这个岛有着网格状的街道布局,在曼哈顿,两个点之间的最短路径是 L1 距离,因为你需要沿着网格行驶。
图片来自作者
我们也可以从零开始实现,或者使用 numpy 函数。
sum(list(map(lambda x, y: abs(x - y), vector1, vector2)))
# 3
np.linalg.norm((np.array(vector1) - np.array(vector2)), ord = 1)
# 3.0
点积
另一种看待向量之间距离的方法是计算点积或标量积。这里是公式,我们可以轻松实现它。
图片来自作者
sum(list(map(lambda x, y: x*y, vector1, vector2)))
# 11
np.dot(vector1, vector2)
# 11
这个度量方法有些难以解释。一方面,它可以告诉你向量是否朝着同一方向指向。另一方面,结果高度依赖于向量的大小。例如,让我们计算两对向量之间的点积:
-
(1, 1)
对比(1, 1)
-
(1, 1)
对比(10, 10)
。
在这两种情况下,向量是共线的,但在第二种情况下,点积大约是第一种情况的十倍:2 对比 20。
余弦相似度
很多时候,会使用余弦相似度。余弦相似度是通过向量的大小(或范数)归一化后的点积。
图片来自作者
我们可以像之前那样自己计算所有内容,或者使用来自 sklearn 的函数。
dot_product = sum(list(map(lambda x, y: x*y, vector1, vector2)))
norm_vector1 = sum(list(map(lambda x: x ** 2, vector1))) ** 0.5
norm_vector2 = sum(list(map(lambda x: x ** 2, vector2))) ** 0.5
dot_product/norm_vector1/norm_vector2
# 0.8575
from sklearn.metrics.pairwise import cosine_similarity
cosine_similarity(
np.array(vector1).reshape(1, -1),
np.array(vector2).reshape(1, -1))[0][0]
# 0.8575
函数cosine_similarity
期望输入 2D 数组。因此,我们需要调整 numpy 数组的形状。
让我们谈谈这个度量的物理意义。余弦相似度等于两个向量之间的余弦值。向量越接近,度量值越高。
图片来自作者
我们甚至可以计算两个向量之间的精确角度(单位为度)。我们得到的结果大约为 30 度,这看起来非常合理。
import math
math.degrees(math.acos(0.8575))
# 30.96
该使用哪种度量方法?
我们已经讨论了计算两个向量之间距离的不同方法,你可能开始思考该使用哪一种方法。
你可以使用任何距离来比较你拥有的嵌入。例如,我计算了不同聚类之间的平均距离。L2 距离和余弦相似度给我们呈现了相似的图像:
-
聚类内的对象比与其他聚类的对象更接近。解释我们的结果有点棘手,因为对于 L2 距离,越近意味着距离越小,而对于余弦相似度——度量越高表示对象越接近。不要感到困惑。
-
我们可以发现某些主题非常接近,例如“政治”和“经济学”,或者“人工智能”和“数据科学”。
图片由作者提供
图片由作者提供
然而,对于 NLP 任务,通常的最佳实践是使用余弦相似度。其背后有几个原因:
-
余弦相似度的范围在 -1 和 1 之间,而 L1 和 L2 距离没有界限,因此余弦相似度更容易解释。
-
从实际角度来看,计算点积比计算欧几里得距离的平方根更有效。
-
余弦相似度受维度灾难的影响较小(我们稍后会讨论它)。
OpenAI 的嵌入已经是标准化的,因此在这种情况下,点积和余弦相似度是相等的。
你可能在上述结果中发现,聚类间距和聚类内距的差异并不大。根本原因是我们向量的高维度。这个现象被称为“维度灾难”:维度越高,向量之间的距离分布越窄。你可以在这篇文章中了解更多细节。
我想简要地展示一下它是如何工作的,以便你能有所直觉。我计算了 OpenAI 嵌入值的分布,并生成了具有不同维度的 300 个向量集。然后,我计算了所有向量之间的距离,并绘制了直方图。你可以清楚地看到,向量维度的增加使得分布变得更窄。
图表由作者提供
我们已经学习了如何衡量嵌入之间的相似度。这样,我们就完成了理论部分,接下来将进入更实际的部分(可视化和实际应用)。让我们先从可视化开始,因为先看到你的数据总是更好的。
可视化嵌入
理解数据的最佳方式是将其可视化。不幸的是,嵌入有 1536 个维度,因此查看数据相当具有挑战性。然而,有一种方法:我们可以使用降维技术将向量投影到二维空间。
PCA
最基本的降维技术是PCA(主成分分析)。让我们尝试使用它。
首先,我们需要将嵌入转换为二维 numpy 数组,以便传递给 sklearn。
import numpy as np
embeddings_array = np.array(df.embedding.values.tolist())
print(embeddings_array.shape)
# (1400, 1536)
然后,我们需要初始化一个 PCA 模型,设置n_components = 2
(因为我们想创建一个二维可视化图),在整个数据上训练模型并预测新值。
from sklearn.decomposition import PCA
pca_model = PCA(n_components = 2)
pca_model.fit(embeddings_array)
pca_embeddings_values = pca_model.transform(embeddings_array)
print(pca_embeddings_values.shape)
# (1400, 2)
结果,我们得到了一个包含每个问题两个特征的矩阵,这样我们就可以轻松地在散点图中可视化它。
fig = px.scatter(
x = pca_embeddings_values[:,0],
y = pca_embeddings_values[:,1],
color = df.topic.values,
hover_name = df.full_text.values,
title = 'PCA embeddings', width = 800, height = 600,
color_discrete_sequence = plotly.colors.qualitative.Alphabet_r
)
fig.update_layout(
xaxis_title = 'first component',
yaxis_title = 'second component')
fig.show()
作者提供的图像
我们可以看到,每个话题中的问题彼此相当接近,这很好。然而,所有的簇都混在一起,所以还有改进的空间。
t-SNE
PCA 是一个线性算法,而现实生活中的大多数关系是非线性的。因此,由于非线性原因,我们可能无法将簇分开。让我们尝试使用非线性算法t-SNE,看看它是否能展示更好的结果。
代码几乎相同。我只是用了 t-SNE 模型而不是 PCA。
from sklearn.manifold import TSNE
tsne_model = TSNE(n_components=2, random_state=42)
tsne_embeddings_values = tsne_model.fit_transform(embeddings_array)
fig = px.scatter(
x = tsne_embeddings_values[:,0],
y = tsne_embeddings_values[:,1],
color = df.topic.values,
hover_name = df.full_text.values,
title = 't-SNE embeddings', width = 800, height = 600,
color_discrete_sequence = plotly.colors.qualitative.Alphabet_r
)
fig.update_layout(
xaxis_title = 'first component',
yaxis_title = 'second component')
fig.show()
t-SNE 的结果看起来好多了。除了“genai”、“datascience”和“ai”,大部分簇都已经分开了。然而,这是预料之中的——我怀疑我能否将这些话题分开。
看着这个可视化结果,我们可以看到,嵌入在编码语义意义方面相当不错。
另外,你可以将数据投影到三维空间并进行可视化。我不确定这是否实用,但将数据以三维方式展示,可能会带来一些深刻的洞察,并且玩转数据也会很有趣。
tsne_model_3d = TSNE(n_components=3, random_state=42)
tsne_3d_embeddings_values = tsne_model_3d.fit_transform(embeddings_array)
fig = px.scatter_3d(
x = tsne_3d_embeddings_values[:,0],
y = tsne_3d_embeddings_values[:,1],
z = tsne_3d_embeddings_values[:,2],
color = df.topic.values,
hover_name = df.full_text.values,
title = 't-SNE embeddings', width = 800, height = 600,
color_discrete_sequence = plotly.colors.qualitative.Alphabet_r,
opacity = 0.7
)
fig.update_layout(xaxis_title = 'first component', yaxis_title = 'second component')
fig.show()
条形码
理解嵌入的方法是将其中一些可视化为条形码,并查看它们之间的相关性。我挑选了三个嵌入示例:两个是最接近的,另一个是我们数据集中最远的示例。
embedding1 = df.loc[1].embedding
embedding2 = df.loc[616].embedding
embedding3 = df.loc[749].embedding
import seaborn as sns
import matplotlib.pyplot as plt
embed_len_thr = 1536
sns.heatmap(np.array(embedding1[:embed_len_thr]).reshape(-1, embed_len_thr),
cmap = "Greys", center = 0, square = False,
xticklabels = False, cbar = False)
plt.gcf().set_size_inches(15,1)
plt.yticks([0.5], labels = ['AI'])
plt.show()
sns.heatmap(np.array(embedding3[:embed_len_thr]).reshape(-1, embed_len_thr),
cmap = "Greys", center = 0, square = False,
xticklabels = False, cbar = False)
plt.gcf().set_size_inches(15,1)
plt.yticks([0.5], labels = ['AI'])
plt.show()
sns.heatmap(np.array(embedding2[:embed_len_thr]).reshape(-1, embed_len_thr),
cmap = "Greys", center = 0, square = False,
xticklabels = False, cbar = False)
plt.gcf().set_size_inches(15,1)
plt.yticks([0.5], labels = ['Bioinformatics'])
plt.show()
作者提供的图
由于维度过高,我们很难直观地判断向量之间的接近程度。然而,我仍然喜欢这种可视化方式。它在某些情况下可能会有帮助,所以我和你分享这个想法。
我们已经学会了如何可视化嵌入,并且对它们抓取文本意义的能力没有任何疑虑。现在,到了最有趣、最吸引人的部分,我们将讨论如何在实际中利用嵌入。
实际应用
当然,嵌入的主要目标并不是仅仅将文本编码为数值向量或仅为了可视化它们而做。我们可以从捕捉文本意义的能力中受益匪浅。让我们通过一些更实际的例子来了解这个过程。
聚类
让我们从聚类开始。聚类是一种无监督学习技术,允许你将数据分成不同的组,而不需要任何初始标签。聚类可以帮助你理解数据中的内部结构模式。
我们将使用最基本的聚类算法之一——K-means。对于 K-means 算法,我们需要指定聚类的数量。我们可以通过silhouette 评分来定义最优的聚类数量。
让我们尝试 k(聚类数量)在 2 到 50 之间的值。对于每个 k,我们将训练一个模型并计算 silhouette 评分。silhouette 评分越高,聚类效果越好。
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
import tqdm
silhouette_scores = []
for k in tqdm.tqdm(range(2, 51)):
kmeans = KMeans(n_clusters=k,
random_state=42,
n_init = 'auto').fit(embeddings_array)
kmeans_labels = kmeans.labels_
silhouette_scores.append(
{
'k': k,
'silhouette_score': silhouette_score(embeddings_array,
kmeans_labels, metric = 'cosine')
}
)
fig = px.line(pd.DataFrame(silhouette_scores).set_index('k'),
title = '<b>Silhouette scores for K-means clustering</b>',
labels = {'value': 'silhoutte score'},
color_discrete_sequence = plotly.colors.qualitative.Alphabet)
fig.update_layout(showlegend = False)
在我们的例子中,当k = 11
时,silhouette 评分达到了最大值。因此,让我们在最终模型中使用这个聚类数量。
图表由作者提供
让我们像之前一样,使用 t-SNE 进行降维来可视化聚类。
tsne_model = TSNE(n_components=2, random_state=42)
tsne_embeddings_values = tsne_model.fit_transform(embeddings_array)
fig = px.scatter(
x = tsne_embeddings_values[:,0],
y = tsne_embeddings_values[:,1],
color = list(map(lambda x: 'cluster %s' % x, kmeans_labels)),
hover_name = df.full_text.values,
title = 't-SNE embeddings for clustering', width = 800, height = 600,
color_discrete_sequence = plotly.colors.qualitative.Alphabet_r
)
fig.update_layout(
xaxis_title = 'first component',
yaxis_title = 'second component')
fig.show()
从视觉上看,我们可以看到算法能够很好地区分聚类——它们分离得非常清晰。
我们有事实性的主题标签,因此我们甚至可以评估聚类的效果。让我们看看每个聚类的主题混合情况。
df['cluster'] = list(map(lambda x: 'cluster %s' % x, kmeans_labels))
cluster_stats_df = df.reset_index().pivot_table(
index = 'cluster', values = 'id',
aggfunc = 'count', columns = 'topic').fillna(0).applymap(int)
cluster_stats_df = cluster_stats_df.apply(
lambda x: 100*x/cluster_stats_df.sum(axis = 1))
fig = px.imshow(
cluster_stats_df.values,
x = cluster_stats_df.columns,
y = cluster_stats_df.index,
text_auto = '.2f', aspect = "auto",
labels=dict(x="cluster", y="fact topic", color="share, %"),
color_continuous_scale='pubugn',
title = '<b>Share of topics in each cluster</b>', height = 550)
fig.show()
在大多数情况下,聚类效果非常好。例如,聚类 5 几乎只包含关于自行车的问题,而聚类 6 则是关于咖啡的。然而,它无法区分一些相近的主题:
-
“ai”、“genai”和“datascience”都在一个聚类中,
-
同一家商店,包含“经济学”和“政治”。
在这个例子中,我们仅使用了嵌入作为特征,但如果你有任何额外的信息(例如提问者的年龄、性别或国家),你也可以将其包含在模型中。
分类
我们可以将嵌入用于分类或回归任务。例如,你可以用它来预测客户评论的情感(分类)或 NPS 评分(回归)。
由于分类和回归是监督学习,你需要有标签。幸运的是,我们知道问题的主题,并且可以训练模型来预测它们。
我将使用随机森林分类器。如果你需要快速复习一下随机森林的相关知识,可以在这里找到。为了正确评估分类模型的性能,我们将把数据集分为训练集和测试集(80%对 20%)。然后,我们可以在训练集上训练模型,并在测试集上测量质量(模型未见过的问题)。
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
class_model = RandomForestClassifier(max_depth = 10)
# defining features and target
X = embeddings_array
y = df.topic
# splitting data into train and test sets
X_train, X_test, y_train, y_test = train_test_split(
X, y, random_state = 42, test_size=0.2, stratify=y
)
# fit & predict
class_model.fit(X_train, y_train)
y_pred = class_model.predict(X_test)
为了评估模型的表现,让我们计算一个混淆矩阵。在理想情况下,所有非对角元素应该为 0。
from sklearn.metrics import confusion_matrix
cm = confusion_matrix(y_test, y_pred)
fig = px.imshow(
cm, x = class_model.classes_,
y = class_model.classes_, text_auto='d',
aspect="auto",
labels=dict(
x="predicted label", y="true label",
color="cases"),
color_continuous_scale='pubugn',
title = '<b>Confusion matrix</b>', height = 550)
fig.show()
我们可以看到与聚类相似的结果:一些主题很容易分类,准确率达到 100%,例如“自行车”或“旅行”,而其他一些则难以区分(尤其是“ai”)。
然而,我们达到了 91.8%的总体准确率,这已经相当不错了。
发现异常
我们还可以使用嵌入来发现数据中的异常。例如,在 t-SNE 图上,我们看到一些问题离它们的聚类非常远,例如,“旅行” 话题。让我们看看这个主题,尝试找出异常。我们将使用 Isolation Forest 算法 来处理它。
from sklearn.ensemble import IsolationForest
topic_df = df[df.topic == 'travel']
topic_embeddings_array = np.array(topic_df.embedding.values.tolist())
clf = IsolationForest(contamination = 0.03, random_state = 42)
topic_df['is_anomaly'] = clf.fit_predict(topic_embeddings_array)
topic_df[topic_df.is_anomaly == -1][['full_text']]
所以,到了这里。我们已经找到了关于旅行话题的最不常见的评论 (来源)。
Is it safe to drink the water from the fountains found all over
the older parts of Rome?
When I visited Rome and walked around the older sections, I saw many
different types of fountains that were constantly running with water.
Some went into the ground, some collected in basins, etc.
Is the water coming out of these fountains potable? Safe for visitors
to drink from? Any etiquette regarding their use that a visitor
should know about?
由于它谈论的是水,这条评论的嵌入接近咖啡话题,因为人们也讨论如何倒水来冲泡咖啡。所以,嵌入表示是非常合理的。
我们可以通过 t-SNE 可视化图来发现它实际上靠近 咖啡 聚类。
作者图
RAG — 检索增强生成
随着 LLM 最近的广泛流行,嵌入已经在 RAG 用例中得到了广泛应用。
当我们有大量文档(例如,来自 Stack Exchange 的所有问题),并且无法将它们全部传递给 LLM 时,我们需要 Retrieval Augmented Generation,因为
-
LLM 有上下文大小的限制(目前,GPT-4 Turbo 的上下文限制为 128K)。
-
我们需要为令牌付费,因此每次传递所有信息会更加昂贵。
-
LLM 在处理更大的上下文时表现较差。你可以查看 Needle In A Haystack — Pressure Testing LLMs 以了解更多细节。
为了能够处理庞大的知识库,我们可以利用 RAG 方法:
-
计算所有文档的嵌入并将其存储在向量存储中。
-
当我们收到用户请求时,我们可以计算其嵌入,并从存储中检索相关文档来回答这个请求。
-
仅将相关文档传递给 LLM 以获得最终答案。
若要了解更多关于 RAG 的信息,不要犹豫,阅读我的文章,里面有更多的细节 在这里.
总结
在本文中,我们详细讨论了文本嵌入。希望现在你已经对这个话题有了完整且深入的理解。以下是我们旅程的简要回顾:
-
首先,我们回顾了处理文本的各种方法的演变。
-
然后,我们讨论了如何理解文本之间是否具有相似的意义。
-
之后,我们看到了不同的文本嵌入可视化方法。
-
最后,我们尝试将嵌入作为特征应用于不同的实际任务,如聚类、分类、异常检测和 RAG。
非常感谢你阅读这篇文章。如果你有任何后续问题或评论,请在评论区留言。
参考文献
本文使用的数据集来自Stack Exchange 数据集,该数据集可在创作共用许可协议下获取。
本文的灵感来源于以下课程:
-
DeepLearning.AI 与 Google Cloud 合作制作的“理解与应用文本嵌入”课程,
-
DeepLearning.AI 与 Weaviate 合作制作的“向量数据库:从嵌入到应用”课程。
使用 GPT 进行文本生成
如何微调 GPT 模型以生成类似 TED 描述的文本
·发布于Towards Data Science ·阅读时长 18 分钟·2024 年 1 月 29 日
--
由Aaron Burden拍摄,图片来源于Unsplash
如果你从事数据科学或机器学习行业,可能已经听说过生成式 AI 这个术语,它指的是能够创造新内容的 AI 算法,如文本、图像或音频。在本文中,我们将深入探讨其中一种生成式 AI 模型:GPT 模型。正如你可能猜到的,GPT 是 ChatGPT 的基础模型,能够生成文本序列。
具体来说,我们将简要讨论 GPT 模型的微调和文本生成过程。虽然市面上有许多现成的库和平台可供我们使用来处理这一任务,但它们往往会抽象掉许多实现细节,这让我们对实际发生的事情感到好奇。
因此,我们将深入探讨微调和文本生成过程的低层细节。这意味着我们将全面覆盖所有内容,从数据预处理、模型构建、设置损失函数、微调过程,到微调模型后文本生成的背后逻辑。
那么,废话少说,让我们开始使用的数据集,这将用于微调我们的 GPT 模型!
关于数据集
使用 Graph Maker 将文本转换为知识图谱
一个开源库,用于使用 Llama 3 和 Mixtral 等开源 LLM 从文本语料库构建知识图谱。
·发表于 Towards Data Science ·阅读时长 11 分钟·2024 年 5 月 7 日
--
图片由作者使用 Adobe Photoshop 生成
在这篇文章中,我将分享一个 Python 库——Graph Maker,它可以根据给定的本体从文本语料库中创建知识图谱。Graph Maker 使用像 Llama3、Mistral、Mixtral 或 Gemma 这样的开源 LLM 来提取知识图谱。
我们将回顾 Graph Maker 的基本概念,简要回顾上一篇文章,以及当前方法如何解决其中的一些挑战。我将在文章结尾分享 GitHub 仓库。
引言
这篇文章是我几个月前写的关于如何将任何文本转换为图的文章的续集。
使用 Mistral 7B 将任何文本语料库转换为知识图谱的方法。
towardsdatascience.com
这篇文章收到了热烈的反响。文中分享的 GitHub 仓库已有超过 180 个 Fork 和超过 900 个 Stars。文章本身在 Medium 上的阅读量超过了 80K。最近,这篇文章在麻省理工学院(MIT)Markus J. Buehler 教授发表的以下论文中被引用。
通过利用生成型人工智能(AI),我们将一个包含 1,000 篇科学论文的数据集进行了转化……
这是一篇引人入胜的论文,展示了在人工智能时代知识图谱的巨大潜力。它展示了知识图谱不仅可以用来检索知识,还可以用来发现新知识。这里是我最喜欢的论文摘录之一。
“例如,我们将展示这种方法如何将看似不相关的概念联系起来,比如贝多芬的第九交响曲与仿生材料科学之间的关系。”
这些进展是对我在上一篇文章中提出的观点的重大确认,并鼓励我进一步发展这些想法。
我还收到了许多同行技术人员的反馈,分享了他们在使用这个仓库时遇到的挑战,以及改善这个想法的建议。我将其中一些建议融入到我在这里分享的新的 Python 包中。
在我们讨论这个包——图形制作器——的工作原理之前,让我们先讨论一下它的“为什么”和“什么”。
简要回顾
我们可能应该从“为什么是图形”开始。然而,我们在我的上一篇文章中简要讨论了这个问题。请随时跳转到那篇文章进行复习。不过,让我们简要讨论一下与我们当前讨论相关的关键概念。
TL;DR 如果你已经熟悉知识图谱的背景,可以跳过这一部分。
这里有一个插图,简洁地总结了知识图谱的理念。
要创建一个知识图谱,我们需要两条信息。
-
知识库:这可以是一个文本语料库、一套代码库、一系列文章等。
-
本体:我们关心的实体类别及其关系类型。我可能在这里对本体的定义做了过于简化的解释,但这足以满足我们的目的。
这里是一个简单的本体。
实体:Person, Place
关系:
Person — 相关于 → Person
Person — 居住在 → Place
Person — 访问 → Place
给定这两条信息,我们可以从提到人物和地点的文本中构建一个知识图谱。然而,假设我们的知识库是关于处方药及其相互作用的临床研究。我们可能会使用一个不同的本体,其中可能包含化合物、用途、效果、反应等作为我们的本体。
在上一篇文章中,我们讨论了如何在不提供本体的情况下,通过 LLM 提取知识图谱。这个方法的核心思想是让 LLM 自主发现最适合给定文本语料的本体。
尽管这种方法缺乏传统生成 KG 的严谨性,但它也有其优点。与传统方法相比,它可以更容易地生成来自非结构化数据的 KG。它生成的 KG 在某种程度上也是非结构化的。然而,它们更易于构建,并且信息更为丰富。它们非常适合像 GRAG(图谱检索增强生成)这样的应用。
为什么选择图谱构建工具?
让我列出一些我在之前的文章反馈中收到的挑战和观察结果。这将帮助我们理解用 LLM 创建 KG 时的挑战。让我们使用《指环王》的维基百科摘要。毕竟,没有人能不爱《指环王》!
有意义的实体
在自由运行的情况下,LLM 提取的实体可能在其类别上过于多样。它会错误地将抽象概念标记为实体。例如,在“比尔博·巴金斯庆祝他的生日并把戒指交给弗罗多”这段文字中,LLM 可能会提取“比尔博·巴金斯庆祝他的生日”或“庆祝他的生日”作为‘动作’。但如果它提取“生日”作为‘事件’,可能会更有用。
一致的实体
它也可能会在不同的上下文中错误地标记同一实体。例如:
‘索伦’,‘黑暗领主索伦’ 和 ‘黑暗领主’ 不应被提取为不同的实体。或者,如果它们被提取为不同的实体,它们应该通过等价关系连接。
解析的韧性
LLM(大语言模型)的输出本质上是非确定性的。为了从大文档中提取知识图谱(KG),我们必须将语料库分割成较小的文本块,然后为每个文本块生成子图。为了构建一致的图谱,LLM 必须按照给定的架构一致地输出 JSON 对象。如果缺少任何一个,可能会不利地影响整个图谱的连通性。
尽管 LLM 在响应时能够更好地生成格式良好的 JSON 对象,但它仍远未完美。具有有限上下文窗口的 LLM 也可能生成不完整的响应。
实体的分类
在识别实体时,LLM 可能会出现较大的错误。当上下文是领域特定的,或者实体没有标准英语命名时,这个问题尤为严重。命名实体识别(NER)模型在这方面做得更好,但它们也受到所训练数据的限制。此外,它们无法理解实体之间的关系。
强迫 LLM 在分类上保持一致是一门提示工程中的艺术。
隐含关系
关系可以被明确提到,也可以通过上下文隐含。例如:
“比尔博·巴金斯庆祝他的生日并把戒指交给弗罗多”隐含了以下关系:
比尔博·巴金斯 → 所有者 → 戒指
比尔博·巴金斯 → 继承人 → 弗罗多
弗罗多 → 所有者 → 戒指
我认为,大型语言模型(LLMs)在某个时刻会超越任何传统的关系提取方法。但目前来说,这是一个需要巧妙提示工程的挑战。
图谱生成器
我在这里分享的图谱生成库在以往方法的基础上做出了改进,通过在严谨性和简易性之间走了一条折中的路——在结构化和非结构化之间走了一条折中的路。与我之前讨论的方式相比,它在上述大部分挑战中表现得更加出色。
与之前的方法不同,在之前的方法中,LLM 可以自行发现本体,而图谱生成器则尝试强制 LLM 使用用户定义的本体。
我们可以通过简单的 pip 命令安装知识图谱生成库。
pip install knowledge-graph-maker
使用给定的本体从任何文本中创建知识图谱。
下面是它如何通过 5 个简单步骤工作的。
这些步骤的代码已包含在我在本文最后分享的笔记本中。
1. 定义你的图谱本体。
该库理解以下本体模式。在幕后,本体是一个 Pydantic 模型。
ontology = Ontology(
# labels of the entities to be extracted. Can be a string or an object, like the following.
labels=[
{"Person": "Person name without any adjectives, Remember a person may be referenced by their name or using a pronoun"},
{"Object": "Do not add the definite article 'the' in the object name"},
{"Event": "Event event involving multiple people. Do not include qualifiers or verbs like gives, leaves, works etc."},
"Place",
"Document",
"Organisation",
"Action",
{"Miscellaneous": "Any important concept can not be categorised with any other given label"},
],
# Relationships that are important for your application.
# These are more like instructions for the LLM to nudge it to focus on specific relationships.
# There is no guarantee that only these relationships will be extracted, but some models do a good job overall at sticking to these relations.
relationships=[
"Relation between any pair of Entities",
],
)
我已经调整了提示,以使结果与给定的本体一致。我认为它做得相当不错。不过,它仍然不是 100%准确。准确度取决于我们选择的生成图谱的模型、应用程序、本体和数据的质量。
2. 将文本拆分成块。
我们可以使用尽可能大的文本语料库来创建大型知识图谱。然而,目前 LLMs 有一个有限的上下文窗口。所以我们需要适当地将文本拆分成块,并逐块地创建图谱。我们应该使用的块大小取决于模型的上下文窗口。此项目中使用的提示大约消耗 500 个 tokens,其余的上下文可以分为输入文本和输出图谱。根据我的经验,较小的 200 到 500 个 tokens 的块会生成更详细的图谱。
3. 将这些块转换为文档。
该文档是一个 Pydantic 模型,具有以下模式。
## Pydantic document model
class Document(BaseModel):
text: str
metadata: dict
我们在文档中添加的元数据会被标记到每个从文档中提取出的关系上。
我们可以将关系的上下文(例如,页码、章节、文章名称等)添加到元数据中。通常情况下,每对节点在多个文档中有多个关系。元数据有助于为这些关系提供上下文。
4. 运行图谱生成器。
图谱生成器直接接受一个文档列表,并对每个文档进行迭代,为每个文档创建一个子图。最终输出是所有文档的完整图谱。
这里是一个简单的例子,展示如何实现这一点。
from knowledge_graph_maker import GraphMaker, Ontology, GroqClient
## -> Select a groq supported model
model = "mixtral-8x7b-32768"
# model ="llama3–8b-8192"
# model = "llama3–70b-8192"
# model="gemma-7b-it" ## This is probably the fastest of all models, though a tad inaccurate.
## -> Initiate the Groq Client.
llm = GroqClient(model=model, temperature=0.1, top_p=0.5)
graph_maker = GraphMaker(ontology=ontology, llm_client=llm, verbose=False)
## -> Create a graph out of a list of Documents.
graph = graph_maker.from_documents(docs)
## result: a list of Edges.
print("Total number of Edges", len(graph))
## 1503
图形制造者将每个文档输入 LLM,并解析响应以创建完整的图形。最终图形是由一系列边组成,每一条边都是像下面这样的 pydantic 模型。
class Node(BaseModel):
label: str
name: str
class Edge(BaseModel):
node_1: Node
node_2: Node
relationship: str
metadata: dict = {}
order: Union[int, None] = None
我已调整了提示,使它们现在能生成相对一致的 JSON。如果 JSON 响应解析失败,图形制造者还会尝试手动将 JSON 字符串拆分成多条边字符串,然后尽力恢复它能找到的部分。
5. 保存到 Neo4j
我们可以将模型保存到 Neo4j 中,既可以创建 RAG 应用程序,运行网络算法,也可以仅仅是通过Bloom来可视化图形。
from knowledge_graph_maker import Neo4jGraphModel
create_indices = False
neo4j_graph = Neo4jGraphModel(edges=graph, create_indices=create_indices)
neo4j_graph.save()
图形的每一条边都会作为事务保存到数据库中。如果你是第一次运行这段代码,请将create_indices
设置为 true。这会通过设置节点的唯一性约束来准备数据库。
5.1 可视化,至少为了好玩 在上一篇文章中,我们使用 networkx 和 pyvis 库可视化了图形。在这里,因为我们已经将图形保存到 Neo4J,我们可以直接利用 Bloom 来可视化图形。
为了避免重复,我们将在本篇文章中生成与上一篇不同的可视化效果。
假设我们想查看书中人物之间关系是如何发展的。
我们可以通过追踪图形在图表制造者浏览书籍时逐步添加的边来实现这一点。为了实现这一点,边模型有一个叫做“order”的属性。这个属性可以用来为图形添加时间性或顺序维度。
在我们的示例中,图形制造者会自动将特定文本块在文档列表中出现的顺序号添加到它从该块提取的每一条边上。所以要查看人物之间关系如何发展,我们只需按边的顺序对图形进行交叉截面。
这里是这些交叉截面的动画。
作者生成的动画
图形和 RAG
这种 KG 的最佳应用可能是在 RAG 中。在 Medium 上有大量的文章讨论如何通过图形增强你的 RAG 应用。
本质上,图形提供了多种不同的知识检索方式。根据我们如何设计图形和应用程序,其中一些技术可能比简单的语义搜索更强大。
从最基本的角度来看,我们可以将嵌入向量添加到节点和关系中,并对向量索引进行语义搜索以进行检索。然而,我觉得图形在 RAG 应用中的真正力量是当我们将 Cypher 查询和网络算法与语义搜索结合时。
我自己也在探索一些这些技术。我希望在下一篇文章中写到它们。
代码
这里是 GitHub 仓库。欢迎随意试用。我还在仓库中包含了一个示例 Python 笔记本,帮助你快速入门。
请注意,在开始之前,你需要在.env文件中添加你的GROQ 凭证。
[## GitHub - rahulnyk/graph_maker
通过在 GitHub 上创建账户,参与 rahulnyk/graph_maker 的开发。
github.com](https://github.com/rahulnyk/graph_maker?source=post_page-----f3f890c0dbe8--------------------------------)
最初,我为一些个人项目开发了这个代码库。我觉得它对更多的应用也会很有帮助。如果你在应用中使用这个库,请与我分享。我非常希望了解你的使用案例。
如果你觉得自己可以为这个开源项目做出贡献,请随时参与,并将其做成自己的项目。
希望你觉得图表生成器有用。感谢阅读。
我是一名架构学习者(不是建筑物的那种…是技术类型的)。过去,我曾从事半导体建模、数字电路设计、电子接口建模和物联网相关工作。
目前,沃尔玛的数据与消费者分析工作让我忙碌不已。
谢谢
文本到 SQL 的 LLM 应用:提示注入
了解你的文本到 SQL LLM 应用如何可能受到提示注入的影响,以及你可以采取的保护数据的缓解措施
·发表于 Towards Data Science ·8 分钟阅读·2024 年 2 月 2 日
--
🔒 非会员文章访问
作者照片,协助来自 Dall-E-3
介绍
最近 LLM(大语言模型)使用的激增为提高我们的工作效率和生产力开辟了许多可能性。其中一个特别令人兴奋的应用是通过基于 LLM 的文本到 SQL 应用程序实现数据分析的民主化。在过去的几个月里,我们看到许多工具相继出现,允许开发者利用 LLM 来实现这一目的,比如 LangChain SQL 代理工具包 和最近的 Vanna AI。
别误会,我认为这些工具对于那些希望在决策过程中更加数据驱动的团队和组织非常有用。但这些工具提供的抽象化简便性带来了一个关键的安全隐患。当你使用这些模块来构建应用时,你失去了对数据库是否真正安全的可见性,也无法精确控制正在执行的查询。而这在面对提示注入的脆弱性时,尤其令人担忧。
文本向量化解密:将语言转化为数据
一份关于文本向量化的直观指南
·发表于 Towards Data Science ·12 分钟阅读·2024 年 8 月 3 日
--
在我上一篇文章中,我们深入探讨了基础模型和大型语言模型(LLMs)。我们尝试了解它们是什么,它们是如何使用的,以及它们的特别之处。我们探索了它们在哪些方面表现优异,哪些方面可能存在不足。我们讨论了它们在理解文本和生成内容等不同领域的应用。这些 LLMs 在自然语言处理(NLP)领域带来了变革。
当我们谈论 NLP 管道时,特征工程(也称为特征提取、文本表示或文本向量化)是一个非常重要且不可或缺的步骤。这个步骤涉及使用技术将文本表示为数字(特征向量)。在处理 NLP 问题时我们需要进行这一步骤,因为计算机无法理解文本,它们只能理解数字,正是这种文本的数值表示需要输入到机器学习算法中,用于解决各种基于文本的应用案例,如语言翻译、情感分析、摘要生成等。
对于我们这些了解机器学习管道的人来说,我们明白特征工程是从模型中获得良好结果的关键步骤。这一概念在 NLP 中同样适用。当我们生成文本数据的数值表示时,我们努力实现的一个重要目标是,这个数值表示生成的应该…
TFT:一个可解释的 Transformer
深入探讨 TFT 及其使用 Darts 实现的过程,以及如何解释一个 Transformer
·发布在Towards Data Science ·12 分钟阅读·2024 年 1 月 5 日
--
介绍
世界上每个公司都需要预测来规划他们的运营,无论他们所处的行业是什么。公司中有多个预测应用场景需要解决,比如年度销售规划、每月根据语言规划客服代理的联系量、根据 SKU 销售计划生产和/或采购等等。
尽管有不同的应用场景,但它们都来自同一个需求:可解释性! 如果你曾经为某个利益相关者部署过预测模型,你可能遇到过这样的问题:“为什么模型会做出这样的预测?”
在这篇文章中,我探讨了 TFT,一个用于时间序列预测的可解释 Transformer。我还提供了一个逐步实现 TFT 的例子,用于使用 Darts(一个 Python 的预测库)预测沃尔玛数据集中的每周销售。最后,我展示了如何解释模型及其在沃尔玛数据集中 16 周预测期的表现。
图 1:可解释的 AI(图像由作者使用 DALL-E 生成)
一如既往,代码可以在Github上找到。
TFT:时间融合 Transformer
它是什么?
全栈数据科学家的 4 个角色
如何成为数据科学领域的“独角兽”
·发布于Towards Data Science ·阅读时间 7 分钟·2024 年 4 月 17 日
--
图片由Amanda Jones提供,来源于Unsplash
这是关于“全栈数据科学”(FSDS)系列文章的第一篇。虽然机器学习(ML)项目的不同方面有着明确的角色分工,但通常仍然需要一个能够管理并实施从头到尾的项目的人。这就是我们所说的全栈数据科学家。在这篇文章中,我将介绍 FSDS 并讨论它的 4 个角色。
什么是全栈数据科学家?
当我第一次学习数据科学(超过 5 年前)时,数据工程和机器学习工程并不像今天这样普及。因此,数据科学家的角色通常定义得更广泛,与现在我们看到的角色有所不同。
数据科学、数据工程和机器学习工程的 Google 趋势—来自Google trends的截图。
例如,数据科学家可能会编写 ETL 脚本、设置数据库、进行特征工程、训练机器学习模型,并将模型部署到生产环境中。
尽管将这些任务分配到多个角色(例如,数据工程师、数据科学家和机器学习工程师)变得越来越普遍,但许多情况下仍然需要那些精通机器学习各个方面的贡献者……
4 个新的潮流 AI 概念及其在数字产品中的潜力
提示、微调、RAG 和代理
·发布于 Towards Data Science ·阅读时间 9 分钟·2024 年 4 月 21 日
--
图片来源:Joshua Coleman 通过Unsplash
各大头条不断更新关于前沿大型语言模型(LLM)新版本的信息,如 Gemini、GPT 或 Claude。在所有这些核心 AI 进展的同时,许多其他公司也在不断探索和实践如何有效利用这些模型进行创新、创造更多价值并降低成本。很容易感到被这些进展压得喘不过气来,我可以说这种情况经常发生在我身上!在这篇博客文章中,我将总结一些最重要的概念及其在产品和公司中的潜力,帮助你跟上这些进展。
有一些常见的潮流概念,围绕着公司如何将 LLM 和其他生成型 AI 模型整合到其产品或流程中。这些概念包括:提示、微调、检索增强生成(RAG)和代理。我相信你之前或多或少听说过其中的几个或全部这些概念,但我觉得有时候这些概念之间的区别并不清晰,更重要的是我们仍然没有意识到它们能为我们的公司或产品提供的潜力。
在这篇博客文章中,我们将概述每一个概念,目标是到最后你能理解它们是什么、它们如何工作……
2024 年你不能忽视的 5 项数据科学技能
用这些关键数据科学技能提升你的职业生涯
·发表于Towards Data Science ·15 分钟阅读·2024 年 6 月 5 日
--
来源:DALL·E
故事时间
我的实习经历
回到 2022 年,我曾在柏林市中心的一家繁忙初创公司担任数据科学家实习生。
我的日子充满了开发和实现自然语言处理(NLP 模型)使用 BERT,以及使用 Faster-RCNN 的计算机视觉模型的挑战。我的任务是提高公司现有模型的准确性,我迫切想要尝试这些模型。
一种新的视角
在一次非正式的会议中讨论我作为数据科学家的成长时,我的导师开始强调一些当时让我有些困惑的事情。
他一直强调专注于模型开发的生产环节的重要性,而不仅仅是实验环节。
他的话让我感到困惑。
就我所知,数据科学家的角色是理解业务需求,进行统计分析,并找到最佳模型。还有什么更多的呢?
弥合通往生产的鸿沟
生成式 AI 的 80/20 问题 — 一项用户体验研究洞察
图片由作者提供
当大型语言模型(LLM)解决任务的正确率达到 80%时,往往只代表用户价值的 20%。
·发表于 Towards Data Science ·阅读时间:3 分钟·2024 年 12 月 21 日
--
帕累托原则认为,如果你解决问题的程度达到 20%,你就能获得 80%的价值。对于生成式 AI 来说,似乎情况正好相反。
关于作者:Zsombor Varnagy-Toth 是 SAP 的高级用户体验研究员,拥有机器学习和认知科学背景。专注于使用定性和定量数据进行产品开发。
我第一次意识到这一点是当我研究专业人士使用大型语言模型(LLMs)撰写营销文案时。我观察到,当这些专业人士开始使用 LLMs 时,他们的热情迅速消退,大多数人最终还是回到了手动编写内容的老路。
这是一个令人完全惊讶的研究发现,因为这些专业人士承认,AI 生成的内容并不差。事实上,他们觉得它出乎意料的好,差不多有 80%的质量。但是,如果是这样,为什么他们还会选择手动创建内容呢?为什么不直接拿 80%好的 AI 生成内容,再手动加上最后的 20%?
这里是直观的解释:
如果你有一首平庸的诗歌,你不能通过换几个词把它变成一首伟大的诗歌。
假设你有一座房子,建得 80%好。它基本上还可以,但墙壁不直,基础也很弱。你无法通过一些额外的工作来修复它。你必须将其拆除,从头开始建造。
我们进一步调查了这一现象,并找到了其根源。对于这些营销专业人士来说,如果一篇文案只有 80%的效果,那么在文本中没有哪个单独的部分可以替换,从而使其达到 100%的效果。为此,整篇文案需要逐段逐句地重新编写。因此,从 AI 的 80%到 100%几乎需要花费与从 0%到 100%手动完成相同的精力。
现在,这带来了一个有趣的启示。对于此类任务,LLM 的价值是“全有或全无”的。它要么做得非常出色,要么就没用。没有中间状态。
我们查看了几种不同类型的用户任务,并发现这种逆帕累托原则影响着特定类型的任务。
-
不容易分解,并且
-
任务规模大,以及
-
期望 100%的质量
如果这些条件中的任何一个没有得到满足,逆帕累托效应就不适用了。
写代码,例如,比写散文更具可组合性。代码有其独立的部分:命令和函数,可以单独挑出并独立修复。如果 AI 将代码完成到 80%,实际上只需要额外花费约 20%的努力,就能达到 100%的结果。
就任务规模而言,LLM 在写短文案(例如社交媒体帖子)方面非常有用。LLM 生成的短内容仍然是“全有或全无”——要么很好,要么毫无价值。然而,由于这些文案的简短性,用户可以一次生成十个,并在几秒钟内找出最好的一个。换句话说,用户无需处理 80%到 100%的问题——他们只需选择最初生成的 100%的变体。
至于质量,有些使用场景并不要求专业级的质量。例如,一个内容工厂可能满足于 80%质量的文章。
这对产品开发意味着什么?
如果你正在构建一个处理大任务且难以分解的 LLM 驱动产品,而用户又要求输出100%质量,你必须围绕 LLM 构建某些东西,将其 80%的表现提升到 100%。这可以是后端的复杂提示方法、额外的微调层,或者是多种工具和代理共同协作的认知架构,用以完善输出。不论这个包装层做什么,它就是带来 80%客户价值的所在。宝藏就埋在那里,LLM 只贡献了 20%。
这一结论与红杉资本的Sonya Huang 和 Pat Grady 的论述一致,即 AI 领域的下一个价值浪潮将由这些“最后一公里应用提供商”创造——这些包装公司弄清楚如何跨越最后一公里,从而创造 80%的价值。
准确性与可解释性的权衡是一个谎言
为什么,从大局来看,黑盒模型并不更准确
·发布于Towards Data Science ·阅读时间:7 分钟·2024 年 10 月 16 日
--
图片由Nathan Cima提供,来源于Unsplash
当我开始做数据科学家时,我本以为会使用最先进的模型。XGBoost、神经网络。这些东西复杂且有趣,肯定能带来改进。然而,我并未料到,模型面临的一个难题是——如何向其他人解释它们。
谁能想到你需要理解你的自动化系统所做的决策?
令我高兴的是,我偶然走进了与模型无关的方法这个兔子洞。通过这些方法,我可以同时享有两全其美的效果。我可以训练黑盒模型,然后通过像 SHAP、LIME、PDPs、ALEs 和 Friedman’s H-stat 等方法来解释它们。我们不再需要为了可解释性而牺牲准确性!
不是那么快。那种思维是错误的。
在我们追求最佳性能的过程中,我们常常忽视了机器学习的真正意义:即在新的未见数据上做出准确的预测。让我们来讨论一下,为什么复杂模型并不总是实现这一目标的最佳方法。即使我们可以通过其他方法来解释它们。
什么是准确性与可解释性之间的权衡?
AI 开发者的困境:专有 AI 与开源生态系统
图片来源:Adobe Stock。
影响生成式人工智能(GenAI)大规模集成和部署的基本选择
·发表于 Towards Data Science ·阅读时间 17 分钟·2024 年 9 月 30 日
--
在公司或开发者采用生成式人工智能(GenAI)之前,他们常常会思考如何从 AI 集成到业务中获得商业价值。考虑到这一点,一个基本问题随之而来:哪种方式能提供最佳的投资回报——是采用一个大型、包罗万象的专有模型,还是使用一个可以根据公司需求进行塑造和微调的开源 AI 模型?AI 采用策略范围广泛,从访问像 OpenAI 的 GPT-4o 这样的专有前沿大模型的云服务,到在公司的计算环境中构建一个使用公司数据索引的小型开源模型来执行一组特定任务的内部解决方案。当前的 AI 解决方案远远超出了模型本身,还包括一个完整的生态系统,其中包含检索系统、代理以及其他功能组件,如 AI 加速器,这些对于大模型和小模型都具有重要意义。跨行业合作的出现,如 Open Platform for Enterprise AI (OPEA),进一步推动了简化访问和构建端到端开源解决方案的承诺。
这种在开源生态系统和专有设置之间的基本选择会影响无数的商业和技术决策,使其成为“AI 开发者的困境”。我认为,对于大多数企业和其他商业部署,最初使用专有模型来了解 AI 的潜力并最小化早期资本支出(CapEx)是合理的。然而,对于广泛的持续部署,在许多情况下,企业将使用基于生态系统的开源定向解决方案,这提供了一种成本效益高、适应性强的战略,能够与不断发展的商业需求和行业趋势保持一致。
GenAI 从消费者向商业部署的过渡
当 GenAI 于 2022 年末凭借 Open AI 的 GPT-3 和 ChatGPT 3.5 闯入市场时,主要吸引了消费者的兴趣。随着企业开始研究 GenAI,两种部署 GenAI 的方法在 2023 年迅速出现——使用像 ChatGPT 这样的巨大前沿模型,还是使用由 Meta 的 LLaMa 模型启发而来的新推出的小型开源模型。到 2024 年初,两种基本方法已逐渐固定,如图 1 中的列所示。采用专有 AI 方法的公司依赖于一个大型封闭模型来提供所需的所有技术价值。例如,以 GPT-4o 作为左侧列的代理,AI 开发者将使用 OpenAI 的技术来提供模型、数据、安全性和计算资源。而采用开源生态系统 AI 方法的公司或开发者,可能会选择合适大小的开源模型,使用公司或私有数据、定制功能以及必要的计算和安全性。
这两种方向都是有效的,并且各有优缺点。这不是一个绝对的划分,开发者可以从任一方法中选择组件,但选择专有的或基于生态系统的开源 AI 路径为公司提供了一个高度一致的战略。虽然预计两种方法都会广泛部署,但我认为,在初步的学习和过渡期后,大多数公司将会采用开源方法。根据使用情况和设置,开源内部 AI 可能带来显著的好处,包括能够微调模型,并利用公司当前的基础设施推动部署,将模型运行在边缘、客户端、数据中心或作为专用服务。随着新的 AI 微调工具的出现,深厚的专业知识已不再是障碍。
图 1. AI 开发者困境的基础方法。图片来源:英特尔实验室。
在各行各业中,AI 开发者正在使用生成式人工智能(GenAI)进行各种应用。2023 年 10 月,Gartner 的调查显示,55%的组织报告称自 2023 年初以来增加了对生成式人工智能的投资,许多公司正在进行生成式人工智能的试点或生产模式。根据调查时的数据,企业主要投资于将生成式人工智能用于软件开发,其次是市场营销和客户服务功能。显然,人工智能应用的范围正在迅速增长。
大型专有模型与小型和大型开源模型
图 2:大型专有模型与小型和大型开源模型的优点。有关商业考虑,请参见图 7 中的资本支出和运营支出方面。图片来源:英特尔实验室。
在我的博客《适者生存:紧凑型生成式 AI 模型是大规模成本效益 AI 的未来》中,我对大型模型与小型模型进行了详细评估。简而言之,自从Meta 于 2023 年 2 月发布的 LLaMa 开源模型以来,学术界和广泛的生态系统进入了一个创新和快速改进的良性循环,创造出比大型前沿模型小 10 倍到 100 倍的高效模型。到 2024 年,许多小型模型的参数数量通常不到 300 亿,它们可以接近匹敌拥有超过 1000 亿参数的 ChatGPT 风格大型模型的能力,尤其是在针对特定领域时。虽然生成式人工智能已经在各行业中广泛部署,用于各种商业用途,但紧凑型模型的使用正在上升。
此外,开源模型通常落后于专有模型仅 6 到 12 个月。使用广泛的语言基准 MMLU,开源模型的改进速度更快,差距似乎正在缩小。例如,OpenAI 的GPT-4o于今年 5 月 13 日发布,带来了重要的多模态特性,而微软的小型开源Phi-3-vision则在 5 月 21 日发布,仅晚了一周。在初步比较中,视觉识别和理解方面,这些模型展示了相似的能力,几项测试甚至偏向 Phi-3-vision 模型。Meta 的 Llama 3.2 开源发布的初步评估表明,其“视觉模型在图像识别和一系列视觉理解任务上与领先的基础模型、Claude 3 Haiku 和 GPT4o-mini 具有竞争力”。
大型模型具有令人难以置信的多功能性。开发者可以选择各种大型商业化的专有 GenAI 模型,包括 OpenAI 的 GPT-4o 多模态模型。谷歌的Gemini 1.5原生多模态模型有四种尺寸:用于移动设备应用开发的 Nano,小型的 Flash 模型用于特定任务,Pro 用于广泛的任务,Ultra 用于高度复杂的任务。此外,Anthropic 的Claude 3 Opus,据说拥有大约 2 万亿个参数,具有 200K 的上下文窗口,允许用户上传大量信息。还有一类即开即用的大型 GenAI 模型,企业可以用来提升员工的生产力和创造性发展。Microsoft 365 Copilot集成了 Microsoft 365 应用套件、Microsoft Graph(来自电子邮件、文件、会议、聊天、日历和联系人等的内容与上下文),以及 GPT-4。
大多数大型和小型开源模型通常对应用框架、工具生态系统、训练数据和评估平台更加透明。模型架构、超参数、响应质量、输入模式、上下文窗口大小和推理成本部分或完全公开。这些模型通常会提供数据集的信息,以便开发者判断是否符合版权或质量要求。这种透明度使得开发者可以轻松地交换模型,以便适应未来的版本。在越来越多的小型商业化开源模型中,Meta 的Llama 3 和 3.1基于 Transformer 架构,提供 8B、70B 和 405B 参数版本。Llama 3.2 多模态模型有 11B 和 90B 版本,较小的版本为 1B 和 3B 参数。与 NVIDIA 合作开发的 Mistral AI 的Mistral NeMo是一个 12B 模型,具有 128k 的大上下文窗口,而微软的Phi-3(3.8B、7B 和 14B)提供用于推理和语言理解任务的 Transformer 模型。微软将 Phi 模型作为“小型语言模型的惊人力量”的例子,同时也在对 OpenAI 的大型模型进行大量投资。微软在生成式 AI(GenAI)领域的多样化兴趣表明,这不是一个一刀切的市场。
模型融合数据(带有 RAG)与检索为中心的生成(RCG)
AI 开发者需要解决的下一个关键问题是在哪里找到推理过程中使用的数据——是在模型的参数化记忆中,还是在模型之外(通过检索可访问)。这可能难以置信,但 2022 年 11 月推出的第一版 ChatGPT 并未访问模型之外的数据。它是在 2022 年 9 月 21 日训练的,且明显无法了解其训练日期之后的事件和数据。这一重大疏忽在 2023 年得到了修正,检索插件被添加进来。如今,大多数模型都与检索前端结合使用,只有在不期望访问大规模或持续更新信息的情况下,如专用编程模型,才会有所例外。
当前的模型通过增强解决方案平台,结合检索增强生成(RAG)前端,使得可以提取模型外部的信息,从而在这一问题上取得了显著进展。高效且安全的 RAG 是企业级 GenAI 部署的要求,微软于 2023 年底推出的GPT-RAG便是一个例证。此外,在博客知识检索成为核心中,我探讨了如何在 GenAI 从消费级部署过渡到商业级部署时,解决方案应主要围绕模型外部的信息构建,采用以检索为核心的生成(RCG)方法。
图 3. RAG 与 RCG 的优势对比。图片来源:英特尔实验室。
RCG 模型可以定义为 RAG GenAI 解决方案的一个特例,专为那些绝大多数数据存在于模型的参数记忆之外,且在预训练或微调过程中大多没有涉及的系统设计。通过 RCG,GenAI 模型的主要角色是解释从公司已索引数据库或其他策划内容中检索到的丰富信息。与其说是记忆数据,不如说是聚焦于针对特定构造、关系和功能的微调。生成输出中的数据质量预计将接近 100%的准确性和时效性。
图 4. GenAI 平台中检索的工作原理。图片来源:英特尔实验室。
OPEA是一个跨生态系统的努力,旨在简化 GenAI 系统的采纳和调优。使用这一可组合的框架,开发者可以创建并评估“开放的、多供应商的、强大的且可组合的 GenAI 解决方案,充分利用生态系统中的最佳创新。”OPEA 有望简化企业级复合 GenAI 解决方案的实施,包括 RAG、代理和记忆系统。
图 5. GenAI 实施的 OPEA 核心原则。 图片来源:OPEA。
通用多功能模型与定制化目标模型的对比
像 GPT-4o、Claude 3 和 Gemini 1.5 这样的模型是通用型的全能基础模型。它们被设计用于执行广泛的 GenAI 任务,包括编码、聊天和总结。最新的模型已经迅速扩展到执行视觉/图像任务,将其功能从单一的大型语言模型扩展到大型多模态模型或视觉语言模型(VLMs)。开源基础模型也朝着集成多模态的方向发展。
图 6. 通用模型与定制化目标模型的优势对比。图片来源:英特尔实验室。
然而,大多数企业选择采用某种形式的专业化,而不是直接使用第一波面向消费者的通用型生成式人工智能(GenAI)模型。当一家医疗保健公司部署 GenAI 技术时,他们不会使用一个通用模型来管理供应链、进行 IT 部门的编码工作以及进行深度医疗分析来管理患者护理。企业会根据不同的使用场景,部署该技术的更多专业化版本。企业可以通过多种方式构建专业化的 GenAI 解决方案,包括领域特定模型、针对性模型、定制化模型和优化模型。
领域特定模型 专注于特定的业务领域或兴趣领域。领域特定模型有专有的和开源的两种类型。例如,BloombergGPT 是一款专门为金融领域设计的 50B 参数专有大语言模型,在多个金融基准测试中超越了更大的 GPT-3 175B 参数模型。然而,小型的开源领域特定模型也可以提供出色的替代方案,正如FinGPT所展示的,它提供了开发金融语言模型(FinLLMs)的可获取和透明的资源。FinGPT 3.3 以 Llama 2 13B 为基础模型,专为金融领域设计。在最近的基准测试中,FinGPT 在多个任务上超过了 BloombergGPT,并在金融基准任务(如 FPB、FiQA-SA 和 TFNS)上轻松战胜了 GPT-4。为了理解这个小型开源模型的巨大潜力,值得注意的是,FinGPT 可以以不到 300 美元的成本进行微调,以融入新的数据。
针对性模型 专注于一类任务或功能,例如针对编码、图像生成、问答或情感分析的单独针对性模型。最近的一个针对性模型例子是英特尔实验室、Hugging Face 和 UKP 实验室联合推出的SetFit。这种针对 Sentence Transformers 进行微调的少量样本文本分类方法,在推理和训练时更为高效,能够在少量标注的训练数据下实现高精度,例如仅用每个类别八个标注样本的客户评价(CR)情感数据集。这个仅有 355M 参数的小型模型可以在多样化的 RAFT 基准测试中超过 GPT-3 175B 参数模型的表现。
需要注意的是,针对性模型与领域特定模型是独立的。例如,像SetFitABSA这样的情感分析解决方案具有针对性的功能,可以应用于工业、娱乐或酒店等多个领域。然而,既具有针对性又具有领域专门化的模型可能会更有效。
定制化模型进一步进行微调和优化,以满足公司、组织或个人的特定需求和偏好。通过对特定内容进行索引以供检索,生成的系统在处理与这些数据(无论是私有还是公开)相关的任务时,变得高度具体且高效。开源领域提供了多种定制模型的选项。例如,Intel Labs 使用直接偏好优化(DPO)对 Mistral 7B 模型进行改进,从而创建了开源的Intel NeuralChat。开发人员还可以通过使用大型语言模型的低秩适应(LoRA)以及其更加节省内存的版本QLoRA,来微调和定制模型。
优化能力可用于开源模型。优化的目标是在保持模型功能和准确性的同时,显著减少其执行负担,从而显著提高成本、延迟和预期平台的最佳执行效率。用于模型优化的一些技术包括蒸馏、剪枝、压缩和量化(至 8 位甚至 4 位)。一些方法,如专家混合(MoE)和推测解码,可以视为执行优化的形式。例如,据报道 GPT-4 由八个较小的 MoE 模型组成,每个模型有 220B 个参数。执行仅激活模型的部分,从而使推理更加经济。
生成即服务云执行与托管执行环境的推理对比
图 7. GaaS 与托管执行的优势对比。图片来源:Intel Labs。
开发人员需要考虑的另一个关键选择是执行环境。如果公司选择专有模型的方向,推理执行将通过 API 或查询调用来完成,这些调用连接到云中运行的模型的抽象化和隐蔽版本。模型的大小及其他实现细节并不重要,除非它们会影响可用性或一些关键费用(按令牌、查询次数,或无限计算许可证收费)。这种方法有时被称为生成即服务(GaaS)云服务,是公司消费非常大的专有模型(如 GPT-4o、Gemini Ultra 和 Claude 3)的一种主要方式。然而,GaaS 也可以用于提供像 Llama 3.2 这样的小型模型。
使用 GaaS 进行外包智能方法有明确的积极方面。例如,访问通常是即时的,并且开箱即用,减轻了内部开发的工作量。还有一个隐含的承诺,即当模型或其环境升级时,AI 解决方案开发者可以在不做大量努力或更改设置的情况下,获取最新的更新。此外,费用几乎完全是运营支出(OpEx),如果工作负载是初步的或有限的,这种方式更为优选。对于早期采用和间歇性使用,GaaS 提供了更多支持。
相比之下,当公司选择内部智能方法时,模型推理周期被纳入并在计算环境和现有的业务软件设置中进行管理。这对于相对较小的模型(2024 年约 30B 参数或更少)以及可能甚至是中等规模的模型(2024 年 50B 到 70B 参数)在客户端设备、网络、本地数据中心或云环境中,使用如虚拟私有云(VPC)等服务提供商的设置,是一个可行的解决方案。
像 Llama 3.1 8B 这样的模型或类似模型可以在开发者的本地机器(Mac 或 PC)上运行。通过使用像量化这样的优化技术,可以在本地环境中实现所需的用户体验。使用像Ollama这样的工具和框架,开发者可以本地管理推理执行。推理周期可以在公司的数据中心通过传统的 GPU、Intel Xeon或Intel Gaudi AI 加速器上运行。如果推理是在服务提供商处运行,则会按基础设施即服务(IaaS)计费,使用公司自己的设置和执行选项。
当推理执行在公司计算环境中(客户端、边缘、本地或 IaaS)进行时,如果超出仅在现有硬件上添加工作负载的范围,则对计算设备的资本支出(CapEx)要求更高。虽然 OpEx 与 CapEx 的比较复杂且依赖于许多变量,但当部署需要广泛、持续、稳定的使用时,CapEx 更为可取。尤其是随着较小模型和优化技术的出现,允许在主流设备和处理器上运行先进的开源模型,甚至在本地笔记本/台式机上运行时,这一点尤为真实。
在公司计算环境中运行推理任务可以更好地控制安全性和隐私性。减少数据的移动和暴露在保护隐私方面非常有价值。此外,在本地环境中运行基于检索的 AI 解决方案时,可以通过细致的控制来应对潜在的隐私问题,允许用户控制对信息的访问。安全性通常被提及为公司部署 GenAI 时的首要关注点,机密计算是一个主要需求。机密计算通过在受信硬件基础上计算,保护数据在使用中的安全,使用的是受信执行环境(TEE)。
较小的开源模型可以在公司的最安全应用环境中运行。例如,运行在 Xeon 上的模型可以在 TEE(受信执行环境)中完全执行,且仅带有有限的开销。如图 8 所示,加密数据在计算过程中得到保护。该模型会检查其来源和完整性,以防篡改。实际执行过程中,模型免受任何破坏,包括操作系统或其他应用程序的干扰,从而防止被不受信任的实体查看或篡改。
图 8. GenAI 的安全要求。图片来源:英特尔实验室。
总结
生成型 AI 是一项正在评估或被大多数各行各业公司积极采用的变革性技术。在 AI 开发者考虑最佳解决方案时,他们需要面对的一个重要问题是,是否选择使用外部专有模型,还是依赖开源生态系统。一条路径是依赖于一个大型的专有黑盒 GaaS 解决方案,使用 RAG,例如 GPT-4o 或 Gemini Ultra。另一条路径则采取一种更加适应性强且具整合性的方式——从一个庞大的开源模型池中挑选小型模型,并根据需要交换,主要利用公司内部信息,定制并优化以满足特定需求,并在公司现有的基础设施中执行。如前所述,这两种基本策略之间可能会有组合选择。
我相信,随着众多 AI 解决方案开发者面临这一核心难题,大多数人最终(经过一段学习期)会选择将开源生成型 AI(GenAI)模型嵌入到他们的内部计算环境、数据和业务环境中。这样,他们将能够利用开源及其广泛生态系统的良性循环推动 AI 创新的巨大进步,同时保持对成本和命运的控制。
让 AI 在解决 AI 开发者困境中拥有最终发言权。在一次 staged AI 辩论,OpenAI 的 GPT-4 与微软的开源 Orca 2 13B 就使用专有与开源生成性 AI 在未来开发中的优劣进行了辩论。以 GPT-4 Turbo 作为裁判,开源生成性 AI 赢得了辩论。获胜的论点? Orca 2 呼吁“更分散、开放、协作的 AI 开发未来,利用全球人才,旨在实现集体进步。该模型有望加速创新,推动 AI 普及,并通过社区治理确保道德和透明的做法。”
了解更多:生成式 AI 系列
知识检索登上舞台:生成性 AI 架构从 RAG 向解释性检索中心生成(RCG)模型转变
适者生存:紧凑型生成性 AI 模型是大规模、成本效益高的 AI 未来
机器是否已经实现了进化性飞跃,可以用人类语言交流?
参考文献
-
你好,GPT-4o。(2024 年 5 月 13 日)。
openai.com/index/hello-gpt-4o/
-
企业人工智能开放平台。(无日期)。企业人工智能开放平台(OPEA)。
opea.dev/
-
Gartner 调查发现 55%的组织正在进行试点或生产中。(2023 年 10 月 3 日)。Gartner。
www.gartner.com/en/newsroom/press-releases/2023-10-03-gartner-poll-finds-55-percent-of-organizations-are-in-piloting-or-production-mode-with-generative-ai
-
Singer, G.(2023 年 7 月 28 日)。适者生存:紧凑型生成性 AI 模型是大规模、成本效益高的 AI 未来。Medium。
towardsdatascience.com/survival-of-the-fittest-compact-generative-ai-models-are-the-future-for-cost-effective-ai-at-scale-6bbdc138f618
-
介绍 LLaMA:一款基础性的 65 亿参数语言模型。(无日期)。
ai.meta.com/blog/large-language-model-llama-meta-ai/
-
392:OpenAI 改进版的 ChatGPT 应让专家和初学者开发者都感到兴奋,&更多内容—ARK 投资。(无日期)。Ark Invest。
ark-invest.com/newsletter_item/1-openais-improved-chatgpt-should-delight-both-expert-and-novice-developers
-
Bilenko, M.(2024 年 5 月 22 日)。Phi-3 系列新增模型,可在 Microsoft Azure 上使用。微软 Azure 博客。
azure.microsoft.com/en-us/blog/new-models-added-to-the-phi-3-family-available-on-microsoft-azure/
-
Matthew Berman。(2024 年 6 月 2 日)。开源视觉 AI — 令人惊讶的结果!(Phi3 Vision 对比 LLaMA 3 Vision 对比 GPT4o)[视频]。YouTube。
www.youtube.com/watch?v=PZaNL6igONU
-
Llama 3.2:通过开放、可定制的模型,革新边缘 AI 和视觉技术。
ai.meta.com/blog/llama-3-2-connect-2024-vision-edge-mobile-devices/
-
Gemini — Google DeepMind。(无日期)。
deepmind.google/technologies/gemini/#introduction
-
介绍下一代 Claude \ Anthropic。(无日期)。
www.anthropic.com/news/claude-3-family
-
Thompson, A. D.(2024 年 3 月 4 日)。The Memo — 特别版:Claude 3 Opus。《The Memo》 by LifeArchitect.ai。
lifearchitect.substack.com/p/the-memo-special-edition-claude-3
-
Spataro, J.(2023 年 5 月 16 日)。介绍 Microsoft 365 Copilot — 你的工作副驾驶 — 官方微软博客。官方微软博客。
blogs.microsoft.com/blog/2023/03/16/introducing-microsoft-365-copilot-your-copilot-for-work/
-
介绍 Llama 3.1:迄今为止我们最强大的模型。(无日期)。
ai.meta.com/blog/meta-llama-3-1/
-
Mistral AI。(2024 年 3 月 4 日)。Mistral Nemo。Mistral AI | 让前沿人工智能触手可得。
mistral.ai/news/mistral-nemo/
-
Beatty, S.(2024 年 4 月 29 日)。虽小却强大:Phi-3 小型语言模型的巨大潜力。微软研究院。
news.microsoft.com/source/features/ai/the-phi-3-small-language-models-with-big-potential/
-
Hughes, A.(2023 年 12 月 16 日)。Phi-2:小型语言模型的惊人力量。微软研究院。
www.microsoft.com/en-us/research/blog/phi-2-the-surprising-power-of-small-language-models/
-
Azure。(无日期)。GitHub — Azure/GPT-RAG。GitHub。
github.com/Azure/GPT-RAG/
-
Singer, G. (2023 年 11 月 16 日). 知识检索成为焦点 — 数据科学前沿. Medium.
towardsdatascience.com/knowledge-retrieval-takes-center-stage-183be733c6e8
-
介绍企业 AI 开放平台. (无日期). Intel.
www.intel.com/content/www/us/en/developer/articles/news/introducing-the-open-platform-for-enterprise-ai.html
-
Wu, S., Irsoy, O., Lu, S., Dabravolski, V., Dredze, M., Gehrmann, S., Kambadur, P., Rosenberg, D., & Mann, G. (2023 年 3 月 30 日). BloombergGPT: 一种面向金融的大型语言模型. arXiv.org.
arxiv.org/abs/2303.17564
-
Yang, H., Liu, X., & Wang, C. D. (2023 年 6 月 9 日). FINGPT: 开源金融大型语言模型. arXiv.org.
arxiv.org/abs/2306.06031
-
AI4Finance-Foundation. (无日期). FinGPT. GitHub.
github.com/AI4Finance-Foundation/FinGPT
-
Starcoder2. (无日期). GitHub.
huggingface.co/docs/transformers/v4.39.0/en/model_doc/starcoder2
-
SetFit: 无需提示的高效少量样本学习. (无日期).
huggingface.co/blog/setfit
-
SetFitABSA: 使用 SetFit 进行少量样本的基于方面的情感分析. (无日期).
huggingface.co/blog/setfit-absa
-
Intel/neural-chat-7b-v3–1. Hugging Face. (2023 年 10 月 12 日).
huggingface.co/Intel/neural-chat-7b-v3-1
-
Hu, E. J., Shen, Y., Wallis, P., Allen-Zhu, Z., Li, Y., Wang, S., Wang, L., & Chen, W. (2021 年 6 月 17 日). LORA: 大型语言模型的低秩适应. arXiv.org.
arxiv.org/abs/2106.09685
-
Dettmers, T., Pagnoni, A., Holtzman, A., & Zettlemoyer, L. (2023 年 5 月 23 日). QLORA: 量化 LLMS 的高效微调. arXiv.org.
arxiv.org/abs/2305.14314
-
Leviathan, Y., Kalman, M., & Matias, Y. (2022 年 11 月 30 日). 通过推测解码实现快速推理. arXiv.org.
arxiv.org/abs/2211.17192
-
Bastian, M. (2023 年 7 月 3 日). GPT-4 拥有超过一万亿个参数 — 报告. THE DECODER.
the-decoder.com/gpt-4-has-a-trillion-parameters/
-
Andriole, S. (2023 年 9 月 12 日). LLAMA、ChatGPT、Bard、Co-Pilot 及其他。大规模语言模型如何成为庞大的云服务,并拥有巨大的生态系统。 Forbes.
www.forbes.com/sites/steveandriole/2023/07/26/llama-chatgpt-bard-co-pilot--all-the-rest--how-large-language-models-will-become-huge-cloud-services-with-massive-ecosystems/?sh=78764e1175b7
-
Q8-Chat LLM:在 Intel® CPU 上高效的生成式 AI 体验. (无日期). Intel.
www.intel.com/content/www/us/en/developer/articles/case-study/q8-chat-efficient-generative-ai-experience-xeon.html#gs.36q4lk
-
Ollama. (无日期). Ollama.
ollama.com/
-
AI 加速 Intel® Xeon® 可扩展处理器产品简介. (无日期). Intel.
www.intel.com/content/www/us/en/products/docs/processors/xeon-accelerated/ai-accelerators-product-brief.html
-
Intel® Gaudi® AI 加速器产品. (无日期). Intel.
www.intel.com/content/www/us/en/products/details/processors/ai-accelerators/gaudi-overview.html
-
保密计算解决方案 — Intel. (无日期). Intel.
www.intel.com/content/www/us/en/security/confidential-computing.html
-
什么是受信执行环境? (无日期). Intel.
www.intel.com/content/www/us/en/content-details/788130/what-is-a-trusted-execution-environment.html
-
Adeojo, J. (2023 年 12 月 3 日). GPT-4 与 Open Orca-2–13B 辩论,结果令人惊讶! Medium.
pub.aimind.so/gpt-4-debates-open-orca-2-13b-with-surprising-results-b4ada53845ba
-
Data Centric. (2023 年 11 月 30 日). 惊人的辩论对决:GPT-4 Turbo 对决 Orca-2–13B — 使用 AutoGen 编程! [视频]. YouTube.
www.youtube.com/watch?v=JuwJLeVlB-w
AI 生产力悖论:为什么更多的工人没有使用 ChatGPT?
真正的障碍不是技术技能——而是思考的时间
·发表于Towards Data Science ·6 分钟阅读·2024 年 10 月 27 日
--
尽管像 ChatGPT 这样的工具具有变革性潜力,我与大多数知识工作者交谈后发现,他们根本不使用它。那些使用的人主要停留在像总结这样的基础任务上。仅有超过 5%的 ChatGPT 用户付费购买 Plus 服务——这是潜在职业用户的一个小部分——这表明,专业用户在利用 AI 进行复杂、高价值工作方面仍然很少。
在谷歌大脑到 Shopify 广告等公司,从事 AI 产品研发已有十多年,我亲眼见证了这一领域的发展。随着 ChatGPT 的崛起,AI 已从像照片整理这类“锦上添花”的工具,发展为为所有知识工作者提供重要生产力提升的工具。
大多数高管都明白,如今的热议不仅仅是炒作——他们迫切希望让公司朝着 AI 发展,因为他们知道 AI 比以往任何时候都更强大、更易于使用。那么,尽管有潜力和热情,为什么广泛的应用进展缓慢呢?真正的障碍在于组织如何看待工作本身。系统性的问题使得这些工具无法成为我们日常工作的一部分。
最终,高管们需要问的问题不是“我们如何使用 AI 来做得更快?或者这个功能能否用 AI 构建?”而是“我们如何使用 AI 创造更多价值?我们应该问哪些问题,但却没有问?”
现实世界的影响
最近,我利用大语言模型(LLMs)——类似于 ChatGPT 这类工具背后的技术——来处理一个复杂的数据结构化和分析任务,这个任务在传统情况下需要跨职能团队的数据分析师和内容设计师花费一个月或更长时间来完成。
由作者使用 Midjourney 生成的图像
这是我使用Google AI Studio在一天内完成的工作:
-
将成千上万行非结构化数据转化为结构化的、有标签的数据集。
-
利用人工智能识别这个新结构化数据中的关键用户群体。
-
基于这些模式,我开发了一种新的分类法,可以支持更好、更个性化的终端用户体验。
值得注意的是,我并没有只是按下按钮,让人工智能完成所有工作。
这需要高度集中的注意力、详细的指令和多次迭代。我花了几个小时来设计精准的提示语,提供反馈(像实习生一样,但使用更直接的语言),并在人工智能偏离方向时重新引导它。
从某种意义上说,我是在将一个月的工作压缩成一天,这对我来说是精神上极为疲惫的。
然而,结果不仅仅是一个更快的过程——它是一个根本上更好且不同的结果。大语言模型(LLMs)揭示了隐藏在非结构化数据中的细微模式和边缘案例,从而创造出传统的预先存在结构化数据分析完全会错过的洞察。
反直觉的真相
关键在这里——理解我们 AI 生产力悖论的关键:我的 AI 成功依赖于得到领导层支持,专门腾出一天时间,与 AI 作为我的思维伙伴,一起重新思考我们的数据处理流程。
这使得深入的战略性思考成为可能——探索那些原本需要数周才能发现的联系和可能性。
这种专注于质量的工作通常会在赶工期的过程中被牺牲,然而,它正是推动突破性创新的动力。悖论的是,大多数人没有时间去弄明白他们如何节省时间。
用于探索的专门时间是大多数产品经理无法承受的奢侈。在不断的压力下要求立即交付结果,大多数人几乎没有一个小时的时间来进行这种战略性工作——许多人能挤出时间做这类探索性工作,唯一的办法就是假装生病。他们被高层指令和紧急的客户需求压得喘不过气来,导致他们对战略方向缺乏掌控力。此外,近期的裁员和行业内的其他削减措施加重了工作负担,许多产品经理不得不每天工作 12 小时,仅仅为了跟上基本任务的进度。
这种持续的压力也阻碍了 AI 在改进执行方面的应用。制定稳健的测试计划或主动识别 AI 可能出现的问题,往往被视为奢侈,而非必需。这种情况会形成一种适得其反的动态:如果修复问题会延迟发布,为什么要用 AI 来识别文档中的问题?如果方向已经从上层设定,为什么还要做额外的研究,了解用户和问题领域?
开辟新航道 —— 投资于人才
给人们时间“弄清楚 AI”是不够的;大多数人需要一些培训,才能了解如何让 ChatGPT 做更多的事情,而不仅仅是总结。然而,所需的培训通常远少于人们的预期。
市场上充斥着由专家教授的 AI 培训课程。尽管一些课程卖的可能是“假药”,但许多讲师确实是有声望的专家。然而,这些课程通常并不适合大多数人作为入门课程。它们既费时又过于技术化,而且很少针对特定的工作领域进行定制。
我取得最佳效果的方法是与个人坐下来,进行 10 到 15 分钟的交流,审计他们当前的工作流程,识别他们可以利用 LLM 更快完成更多工作的地方。你不需要理解令牌预测背后的数学原理,就能写出一个好的提示。
不要相信 AI 仅适用于那些技术背景、年龄在四十岁以下的人的误解。根据我的经验,注重细节和对做出最佳工作成果的热情,才是成功的更好指标。试着放下你的偏见——你可能会对谁会成为你的下一个 AI 先锋感到惊讶。
我自己的父亲,一位六十多岁的律师,只用了五分钟就理解了大语言模型(LLMs)能做什么。关键在于将示例量身定制为他的领域。我们想出了一个略显复杂的法律灰色地带,我让 Claude 用边缘案例向一名一年级法学学生解释。他看了这个回答后,立刻明白了自己如何能利用这项技术做十几个不同的项目。二十分钟后,他已经完成了草拟他几个月来一直打算写的法律评论文章的一半。
很可能,你的公司已经有了一些 AI 爱好者——这些“隐藏的宝藏”已经主动在工作中探索大语言模型(LLMs)。这些“LLM 低语者”可能是任何人:工程师、市场营销人员、数据科学家、产品经理或客户服务经理。发出招募创新者的号召,充分利用他们的专业知识。
一旦你识别出这些内部专家,邀请他们进行一到两小时的“AI 审计”,审查你团队当前的工作流程,并识别需要改进的地方。他们还可以帮助创建针对特定用例的起始提示,分享他们的 AI 工作流程,并提供关于如何排除故障和未来评估的建议。
除了节省外部顾问的费用——这些专家更有可能理解你公司系统和目标,因此更容易发现切实且相关的机会。那些犹豫不决的人在看到同事使用这些技术时,也更有可能进行尝试,而不是看到“AI 专家”在使用。
除了确保人们有学习的空间外,一旦他们理解了 AI 工具的能力,还需要确保他们有时间在自己的领域中探索和实验这些工具。公司不能仅仅告诉员工“用 AI 创新”,同时又要求他们在周五下午 5 点之前交出下一个月的功能。确保你的团队每月有几个小时用于探索。
一旦你克服了 AI 采用的第一个障碍,你的团队应该能够识别出最具潜力的投资领域。此时,你将能够更好地评估是否需要任何额外的、更专业的培训。
结论
AI 生产力悖论并不在于技术的复杂性,而在于组织如何看待工作和创新。驾驭 AI 的力量比“AI 影响者”推销最新认证时所说的要简单——通常只需几分钟的有针对性训练。但这要求领导者思维方式的根本转变。高管们不应再堆积短期交付任务,而应为探索和深度、开放性、目标驱动的工作创造空间。真正的挑战不是教员工使用 AI,而是给他们时间和自由,重新定义他们的工作方式。
想深入了解有效的 AI 实施吗?请查看我们需要提高 AI 产品经理的标准和什么才是一个真正的 AI 代理?重新思考自主性的追求。
让 Google 变成 Google 的算法
PageRank 如何改变了我们搜索互联网的方式,以及它为何在 LLMs 与图形 RAG 中仍然发挥着重要作用。
·发布于 Towards Data Science ·阅读时间:16 分钟·2024 年 12 月 18 日
--
由 DALL-E 生成的图像
在 1990 年代末,两位斯坦福大学的研究生,拉里·佩奇和谢尔盖·布林,在他们的博士研究中遇到了一个有趣的想法。
拉里特别着迷于网页之间如何相互链接的方式。他将互联网看作一个庞大的引用网络,就像学术论文互相引用一样。这激发了一个想法:如果一个网页的重要性可以通过其他页面链接到它的数量来衡量,会怎样? 但不仅如此——如果这些链接页面的重要性也很重要呢?
对这个想法产生了兴趣后,拉里开始构建一个后来被命名为“PageRank”(这是他姓氏的巧妙变换)的算法。这个算法将每个指向网页的链接视为对网页的信任投票,但有一个 twist ——来自更重要页面的投票具有更高的权重。
对拉里的这个概念感兴趣的谢尔盖·布林加入了他。两人一起从宿舍开始,构建了一个将使用这一排名系统的搜索引擎。最初,他们将他们的项目命名为“BackRub”,因为它分析了反向链接。
自主管理代理的构成
一种自主管理代理的蓝图:在 Agentic Mesh 生态系统中的自主管理代理
·发布于Towards Data Science ·阅读时间 17 分钟·2024 年 12 月 17 日
--
自主管理代理的构成
最近科技巨头的巨大投资几乎可以确保自主管理代理生态系统很快就会到来。那么,什么是“自主管理代理”呢?
Sebastian Thielke,AWS 平台经济学负责人,描述如下(转述):“自主管理代理对环境刺激做出反应,在追求目标时具有主动性,具备社会互动能力,并能持续学习和改进。”维基百科提供了类似的定义:“自主管理代理是生活在某些复杂动态环境中的计算系统,在该环境中自主感知和行动,并通过这样做实现其设计目标或任务。”在我之前的文章中,我提供了以下定义,我认为它既能涵盖前述定义,也能更加准确地描述:自主管理代理使用 Agentic AI(复杂推理和迭代规划)独立计划并执行任务。
在说到这些之后,我想集中讨论本文的主要内容:一个自主管理代理(以下简称“代理”)的架构是什么样的,它的主要组成部分有哪些?创建一个能够规划和执行任务的“智能”代理需要具备哪些能力?并且由于没有任何代理是孤立存在的,而是必须在一个生态系统中运作……
AQLM 量化算法解析
·发表于Towards Data Science ·13 分钟阅读·2024 年 3 月 13 日
--
市面上有一种新的量化算法!语言模型的加性量化(AQLM) 1 量化过程在 2024 年 2 月初发布,且已被集成到HuggingFace Transformers(自版本4.38.0–2024 年 2 月 21 日)和HuggingFace PEFT(自版本0.9.0–2024 年 2 月 28 日)。这意味着,使用 AQLM 量化的检查点可以通过这些库加载,并且可以使用 HuggingFace Transformers 通过 AQLM 对兼容的检查点进行量化。
在这篇博客文章中,我们将探讨 AQLM 论文1中提出的关键结果,并提供对这一新型量化技术背后关键概念的详细概述。
本文将首先回顾 AQLM 论文中呈现的关键结果。接着,我们将探讨对大规模语言模型进行推理量化的动机。然后,我们将深入分析 AQLM 独特采用的多代码本量化(MCQ)技术,作为权重量化的一种方法。在分解 AQLM 模型的内存占用并检查关键量化参数后,我们将逐步解释 AQLM 量化过程。最后,我们将讨论帕累托效率的概念,探讨其与模型量化的关系,并从中提供关于 AQLM 如何推动帕累托最优量化边界的视角。
AQLM 性能
现有的仅权重量化算法在技术上可以将模型权重量化到 2 位范围。然而,它们在有效保持模型准确性方面失败了。AQLM 是一种新的仅权重后训练量化(PTQ)算法,为 2 位每参数范围设定了新的最先进水平。与现有方法相比,它在 3 位和 4 位范围内也提供了较小的基准改进(见表 1)。具体来说,AQLM 超越了像 GPTQ 2 这样的流行算法,以及更近期但知名度较低的方法,如 QuIP 3 和 QuIP# 4。AQLM 的作者还声称,他们的量化算法首次将模型准确性与内存占用之间的帕累托前沿推向了每参数低于 3 位的范围。
下表总结了在将 Llama-2–70B 模型压缩为每参数 4 位、3 位和 2 位时 AQLM 的表现。性能通过 WikiText2 5 和 C4 [6] 数据集上的困惑度(越低越好)以及 WinoGrande [7] 和 HellaSwag [8] 基准上的零-shot 准确率(越高越好)来衡量。为了对比,表中展示了 QuIP# 这一顶级竞争方法在 4 位和 2 位压缩下的表现。由于 现有的 QuIP# 实现不支持 3 位压缩,因此 SpQR [9] 被作为 AQLM 在 3 位压缩下的对比方法。
表 1 — AQLM 与顶级竞争者在 Llama-2–70B 模型压缩为每参数 2 位、3 位和 4 位时的对比
尽管与 FP16 相比,量化有时能够减少推理延迟,但这并不是一定的。在基准测试中,AQLM 量化的模型显示出适度的延迟改进,大多数情况下速度提高在 1.2 倍到 2 倍之间,最好的情况下可达到 3.05 倍。然而,延迟减少并不是 AQLM 设计者的主要关注点。他们的优先考虑是,在目标模型大小范围内最大化准确性,而不是优化速度。因此,AQLM 量化所带来的延迟增益是显著的,但不像其他现有量化算法的改进那么剧烈。
然而,AQLM 标志着使大规模语言模型在消费者硬件和移动设备上更加可访问的一个重要步骤。例如,将一个 7B 模型从 16 位半精度格式(如 FP16,每个参数 16 位或 2 字节)量化到每个参数仅 2 位(每个参数 0.25 字节),其内存占用减少了 8 倍——从 14GB 减少到仅 1.75GB。
为什么以及量化什么?
PTQ 方法分为两类:一种是仅量化模型权重,另一种是量化权重和激活函数。AQLM 属于第一类,仅量化权重。模型权重在定义上是静态的,因此可以在部署前离线量化,甚至可以分发到HuggingFace 模型库等平台。激活函数包含所有其他内容,包括键值(KV)缓存,只有在推理时的运行时才能得知。
使用 AQLM 量化的第一个检查点(大多量化为 2 位)已开始出现在HF Hub上。然而,流行的模型量化工具TheBloke尚未将这种量化技术纳入其量化方法集中。
在量化 LLM 权重时,并非所有权重都被量化。通常,只有构成参数总数大部分的参数,如注意力层和前馈层的大型投影矩阵,才会被量化。其他参数通常保持原精度。
在选择仅对权重进行量化时,矩阵乘法的高效混合精度内核通常不可用。因此,量化后的权重在运行时从内存中获取后会进行反量化。根据反量化的开销,较低数据传输带来的延迟减少可能会部分保留或完全抵消。
与量化模型在 LLM 推理中的权重内存占用减少相关的四大主要优势:
通过减少权重的内存占用,量化大规模语言模型权重进行推理提供了四大主要优势:
-
减少模型服务的硬件要求:量化模型可以使用更便宜的 GPU 进行服务,甚至可以在消费者设备或移动平台上提供访问。
-
为 KV 缓存提供更多空间,以支持更大的批处理大小和/或序列长度。
-
更快的解码延迟。由于解码过程受限于内存带宽,减少的权重大小直接减少了数据移动,除非被反量化开销所抵消。
-
更高的计算与内存访问比(通过减少数据移动),即算术强度。这允许在解码期间更充分地利用可用的计算资源。
什么是多词典量化(MCQ)?
AQLM 应用多码本量化(MCQ)来压缩 LLMs 的权重。 最初,MCQ 是为了在向量数据库上实现高效的最近邻搜索而开发的。它的工作原理是将数据库中的每个向量分割成子组(子向量),这些子组再通过学习到的向量来近似,称为码字。一个码本是一组这样的码字。这使得相似度计算可以通过有限的码字集来高效地进行,而不是使用完整的向量数据库。
在 AQLM 中,量化的向量对应于权重矩阵的行。也就是说,AQLM 使用 MCQ 对每个权重矩阵的输出通道进行量化。
注意: 应注意,AQLM 使用W.X符号约定(W和X分别是权重矩阵和激活矩阵),而其他一些量化论文使用相反的X.W符号约定。这意味着 AQLM 量化的输出通道对应于权重矩阵的行,而在X.W符号约定中,它们将是列。
权重矩阵的每一行,其形状为(d_out, d_in),被划分为大小为(1, g)的子向量,称为组。假设码本已经被学习,AQLM 将每个组近似为由M个相同大小的 码字组成的和,这些码字以原始精度存储。每个码字属于不同的码本,每个码本包含2B*个码字。为了使用学习到的码本重建一个组,我们实际上只需要存储每个组成码字在其码本中的索引。这个索引可以表示为一个*2B维的独热向量,称为代码。因此,每个组由M个大小为2^B的独热码向量表示。存储这样的独热向量需要B位。因此,存储每个组的压缩表示所需的总内存占用是M x B位。
AQLM 中构建量化表示的过程总结在图 1 中。需要注意的是,在将每个输出通道分割成组之前,输出通道会先由学习到的缩放因子进行缩放。
图 1 — 参数组的多码本编码(d_in=9,d_out=4,g=3,M=3,B=2)— 图由作者提供
如前所述,在推理时,与激活值X的矩阵乘法使用去量化的原始精度参数,而不是量化后的代码向量。如图 2 所示,去量化过程通过将代码向量解压回独热索引向量,进而从每个码本中检索对应的码字。这些码字被加总在一起,然后进行缩放,以再现原始的半精度权重值进行计算。
图 2 — 从码本索引(代码)解码参数组(d_in=9,d_out=4,g=3,M=3,B=2)— 图由作者提供
AQLM 量化模型的内存占用
最重要的是,使用 AQLM 时,每个参数的平均比特数是多少?为了存储 AQLM 量化的权重矩阵,需要存储以下信息:
-
M 个码本,每个码本包含 2^B 个码字,且以原生 16 位精度存储。每个码字的大小为 (1, g)。
-
d_out 缩放因子,每个存储为 16 位浮动数。
-
M 个编码向量,每个 B 比特,用于编码每个组,组数为 d_out x d_in/g。
因此,每个参数的平均比特数可以通过以下公式计算:
应注意,上述公式计算的是单个权重矩阵(即单层)的每个参数的平均比特数,而不是整个模型的平均比特数。
让我们以 Llama-2-70B 前馈层为例,来看一下不同配置(表 2)中每个项的贡献:
为了理解在不同配置下每个项的贡献,让我们以 Llama-2-70B 模型的前馈层(d_in=8 192 和 d_out=28 672)为例。表 2 展示了该层在不同配置下每个项的贡献分解。
表 2 — 分解后的每个参数的平均比特数。场景 A:g=8;M=1;B=16(2 位)— 场景 B:g=8;M=2;B=12(3 位)— 场景 C:g=8;M=2;B=16(4 位)— 场景 D:g=32;M=6;B=16(3.85 位)
缩放因子项的贡献始终可以忽略不计。每个参数的平均比特数主要由编码每个组的码词决定。除非 B 和 g 都设置为相对较高的值(如场景 D),否则码本项通常贡献较小。
关键 AQLM 量化参数
组大小 g、码本数量 M 和码本大小 B 是 AQLM 量化过程中的超参数。假设编码每个组的码词主导了每个参数的平均比特数,我们可以通过 B.M/g 来近似计算总比特数。这意味着多种 g、M 和 B 的组合可以满足相同的整体比特预算。为了选择最佳配置,我们需要检查这些参数对模型性能的影响。
注意: AQLM 量化模型的命名遵循 XBit-MxB
的命名规则,例如 ISTA-DASLab/gemma-2b-AQLM-2Bit-1x16-hf
,表示 Gemma-2B 的 2 位量化版本,使用一个包含 65,536(2¹⁶)个码字的码本。通过了解总比特预算、M 和 B,我们可以轻松推导出 g。
关于延迟,码字数越多,速度越慢,即延迟加速效果越低。例如,在 GPU(Nvidia RTX 3090)上进行 2 位 1x16(共 65,536 个码字)Llama-7B 模型的矩阵-向量乘法时,相较于 FP16 模型,速度提升为 x1.31,而相同规模的 2x8(共 512 个码字)模型则实现了 x1.57 的加速。
然而,减少码字的数量会对模型准确性产生负面影响。举个例子,论文展示了 1x16 的 Llama-7B 模型(2 位范围)在 WikiText2 5上的困惑度为 6.29,而相同模型的 2x8 变体在同一数据集上的困惑度为 7.98。相比之下,FP16 版本的困惑度为 5.12。
现在,考虑一个固定的总位预算(例如 2 位)和代码本大小 B(例如 B=8),有多个有效的(M, g)组合满足预算约束。例如,对于 B=8,(1, 4)、(2, 8)、...、(8, 32) 等组合是有效的配置。论文展示了在给定预算下,较大的(M, g)值与较低的困惑度相关,即减少量化误差,尽管收益递减。这揭示了一个延迟与准确性的权衡——更高的 M 提高了准确性,但也增加了延迟。
注意: 对于许多量化方法,每个参数的平均位数由用于存储参数的精度决定,例如 INT8、INT4、INT3 等。这仅允许几个离散的平均位大小。相比之下,AQLM 提供了更多的灵活性——通过调整 g、M 和 B 超参数,可以在更细的粒度下实现更广泛的平均位数范围(如表 3 所示)。
表 3 — 使用不同(B, M, g)值量化的 Llama-2–70B 前馈层每个参数的平均位数
注意: 忽略模型准确性,可能并非所有配置都是同样高效的。例如,如果 B 的值不是 8 的倍数,那么每个存储的代码并没有充分利用表示它所需的字节中的所有位。
AQLM 量化过程
在前一节中,我们假设代码本和代码已经学习完毕,以便演示 AQLM 如何构建压缩表示。实际上,使用 AQLM 对模型进行量化涉及学习这些代码本。 一旦代码本学习完成,使用上述过程压缩权重矩阵就变得简单了。
对于输入的半精度权重矩阵 W,AQLM 量化过程学习:M 个代码本 C,d_out 个缩放因子 s,以及每个组的 M 个代码向量 b。这些是通过最小化以下损失函数来学习的:
要学习代码本和代码,需要校准数据(即训练数据)。作者使用了来自 RedPajama-v1 数据集[10]的几百个 4096 长度的序列作为校准数据。性能通过在 WikiText2 5和 C4 [6]数据集上评估困惑度来衡量,这些数据集作为验证集。
考虑这个特定训练的技术细节会让我们深入到代码本学习的独特性中。我们只会覆盖 AQLM 训练(因此也是量化)过程的主要步骤。
AQLM 算法实际上应用于每个 Transformer 解码器块。对于给定的解码器块,量化是一个两步过程:
-
每个线性层的代码本、缩放因子和代码都是为该块中的每个线性层学习的。在每种情况下,损失函数最小化发生在两个阶段:1. 先使用初始化的代码本和缩放因子来学习代码。在这里,代码本是固定的,通过残差 k-means 方法初始化。2. 在第一阶段学习的代码保持固定后,代码本和缩放因子将从其初始化值开始进行更新。
-
在对解码器块的每个线性层进行量化后,该块的代码本、缩放因子和非量化参数(如归一化层的缩放/偏置)会进一步微调。在这一阶段,代码保持冻结。这个微调过程使用在量化之前记录的输入和输出激活,并允许对跨层的参数进行联合优化。联合优化考虑了跨层量化误差之间的相互作用,这在非常低的位速率下尤为重要,因为此时量化误差相对较大。
帕累托最优性
AQLM 的作者声称首次将模型准确性(例如通过困惑度度量)与内存占用之间的帕累托前沿推到了每个权重低于 3 位的水平。尽管这是一个重要的成就,但这个里程碑代表了什么呢?
帕累托最优性指的是一种高效的状态,其中一个度量无法在不负面影响另一个度量的情况下得到改善。例如,考虑一个由两个期望特性描述的系统。一个帕累托最优状态是指不存在任何修改可以在不恶化另一个特性的情况下改善一个特性。相反,如果一个变化可以在不影响另一个特性的前提下正面影响一个特性,那么这个变化将被认为是帕累托低效的,因为可以实现一个更优的状态。帕累托前沿描绘了所有这样的帕累托最优状态。
当应用于模型量化时,每个模型变体(无论是量化的还是全精度的)表示一个由其准确性和内存占用描述的状态。帕累托前沿包含了一组(通常是量化的)模型,这些模型在准确性和大小之间达到了最佳权衡。在这个前沿上,无法进一步压缩模型大小而不损失准确性,也无法在不增加内存要求的情况下提高准确性。
例如,论文显示使用 AQLM 对 Llama-2-13B 进行 2 位量化后,困惑度为 5.65,而 Llama-2-7B 的 4 位 AQLM 量化则达到 5.21。两者的内存占用都约为 1.7GB,但 2 位模型的准确性较差。因此,在这个内存占用下,4 位模型更高效——在相同的 1.7GB 大小下具有更高的准确性。
这怎么可能呢?这些帕累托效率限制源于量化技术在极低位比的情况下,避免在准确性上造成重大损失的困难。
如果我们假设所有量化技术都能完美地保持模型准确性,那么每当一种新技术实现更高的压缩率时,帕累托前沿将简单地移动,只包括使用该最新技术量化的模型(图 3)。
图 3 — 完美的量化方法 — 作者提供的图
然而,由于量化会导致模型准确性的损失,压缩率更高并不一定意味着能够达到帕累托前沿,尤其是当与其他现有技术相比,准确性损失过大时(图 4)。
图 4 — 不完美的量化方法 — 作者提供的图
将帕累托前沿推向低于每个权重 3 比特意味着现有的低于 3 比特量化模型并非帕累托最优——对于给定的模型内存占用,准确性没有得到最大化。作者确定 2.5 比特是 Llama-2 系列在 AQLM 下的最佳率。换句话说,Llama-2 模型如果量化到每个参数平均使用 2.5 比特并采用 AQLM,它们就处于帕累托前沿。
结论
在这篇文章中,我们介绍了 AQLM,这是一种首次将多字典量化(MCQ)应用于大型语言模型的新量化算法。AQLM 在每个参数 2 比特范围内设定了模型压缩的新最先进水平,并首次实现了低于 3 比特模型的帕累托最优性。
凭借其开创性的压缩率和对准确性的保持,AQLM 代表了高效部署大型语言模型的重要进步,使得大型语言模型更容易在消费者硬件和移动设备上实现。
AQLM 已经得到了 HuggingFace Transformers 和 PEFT 库的支持,使开发者可以轻松利用 AQLM 的优势!
1: V. Egiazarian 等人,通过加性量化极限压缩大型语言模型(2024 年),arXiv 预印本 arXiv:2401.06118
2: E. Frantar 等人,GPTQ:生成预训练变换器的精确后训练量化(2022 年),ICLR 2023
3: J. Chee 等人,QuIP:具有保证的 2 比特大型语言模型量化(2023 年),NeurIPS 2023 亮点
4: A. Tseng 等人,QuIP#:通过 Hadamard 不相干性和格子字典提高的 LLM 量化(2024 年),arXiv 预印本 arXiv:2402.04396
5: S. Merity 等人,指针哨兵混合模型(2016 年),ICLR 2017 海报
[6]: C. Raffel 等人,探索统一文本到文本转换器的迁移学习极限(2019 年),JMLR 2020
[7]: K. Sagaguchi 等人,WinoGrande:大规模对抗性 Winograd 范式挑战(2021 年),ACM 2021
[8]: R. Zellers 等人,HellaSwag: 机器真的能完成你的句子吗? (2019),ACL 2019
[9]: T. Dettmers 等人,SpQR: 一种用于近乎无损 LLM 权重压缩的稀疏量化表示 (2023),arXiv 预印本 arXiv:2306.03078
[10]: Together Computer,RedPajama: 用于训练大语言模型的开放数据集 (2023),github.com/togethercomputer/RedPajama-Data
《神秘网络》
如何使用网络科学和 Python 绘制出这部流行剧集
·发布于面向数据科学 ·阅读时长 7 分钟·2024 年 12 月 2 日
--
《神秘之境》第二季是 Netflix 最近的一部热门剧集,改编自全球最受欢迎的在线电子游戏之一《英雄联盟》的宇宙。剧集设定在一个重蒸汽朋克风格的幻想世界中,以惊人的视觉效果和创纪录的预算收尾。作为一名网络和数据科学家,特别热衷于将流行文化项目转化为数据可视化,这就是我在完成最后一季后所需要的一切,目的是绘制出隐藏的联系,并将《神秘之境》的故事情节转化为网络可视化——使用 Python。因此,在本教程结束时,您将掌握如何创建并可视化《神秘之境》背后的网络。
然而,这些技能和方法绝不是这部故事所特有的。事实上,它们突出了网络科学提供的一般方法,用于绘制、设计、可视化和解释任何复杂系统的网络。这些系统可以从交通和 COVID-19 传播的网络模式,到大脑网络,再到各种社交网络,例如《神秘之境》系列中的网络。
所有图像由作者创作。
1. 收集角色列表
由于我们在这里要绘制出所有角色背后的联系,首先,我们需要获取每个角色的列表。为此,《神秘之境》粉丝维基网站是一个很好的免费使用信息来源(CC BY-SA 3.0),我们可以通过简单的网页抓取技术轻松访问。具体来说,我们将使用 urllib 进行下载,使用 BeautifulSoup 提取每个角色在主角页面上列出的名字和粉丝维基个人资料链接。
首先下载角色列表网站的 HTML:
import urllib
import bs4 as bs
from urllib.request import urlopen
url_char = 'https://arcane.fandom.com/wiki/Category:Characters'
sauce = urlopen(url_char).read()
soup = bs.BeautifulSoup(sauce,'lxml')
然后,我提取了所有潜在相关的名字。通过右键点击一个想要的元素(在这个案例中是角色档案),并选择浏览器中的元素检查选项,您可以轻松找出要传递给解析的 html(存储在‘soup’变量中)的标签。
从中,我了解到,角色的名字和网址存储在包含‘title=’的行中,但不包含‘:’(对应于类别)。此外,我创建了一个still_character
标志,它帮助我确定角色列表页面上的哪些子页面仍属于故事中的合法角色。
import re
chars = soup.find_all('li')
still_character = True
names_urls = {}
for char in chars:
if '" title="' in str(char) and ':' not in char.text and still_character:
char_name = char.text.strip().rstrip()
if char_name == 'Arcane':
still_character = False
char_url = 'https://arcane.fandom.com' + re.search(r'href="([^"]+)"', str(char)).group(1)
if still_character:
names_urls[char_name] = char_url
前面的代码块将创建一个字典(‘names_urls’),它以每个角色的名字和网址作为键值对存储。现在,让我们快速查看一下我们得到的内容,并打印出名字-网址字典以及它的总长度:
for name, url in names_urls.items():
print(name, url)
这个代码块的输出样本,我们可以测试每个链接——指向每个角色的传记档案:
print(len(names_urls))
哪个代码单元返回 67 的结果,这意味着我们需要处理的命名角色总数。这表示我们已经完成了第一项任务——我们有一个全面的角色列表,并且可以轻松访问它们在粉丝维基网站上的完整文本档案。
2. 收集档案
为了绘制两个角色之间的关系,我们需要找出一种方法来量化两个角色之间的关系。为了捕捉这一点,我依赖于这两个角色的传记相互提及的频率。在技术层面上,为了实现这一点,我们需要收集我们刚刚获得链接的完整传记。我们将通过简单的网页抓取技术再次获取这些信息,然后将每个网站的源代码分别保存在本地文件中,如下所示。
# output folder for the profile htmls
import os
folderout = 'fandom_profiles'
if not os.path.exists(folderout):
os.makedirs(folderout)
# crawl and save the profile htmls
for ind, (name, url) in enumerate(names_urls.items()):
if not os.path.exists(folderout + '/' + name + '.html'):
fout = open(folderout + '/' + name + '.html', "w")
fout.write(str(urlopen(url).read()))
fout.close()
到了本节末,我们的文件夹‘fandom_profiles’应包含每个《Arcane》角色的粉丝维基档案——准备好在我们构建《Arcane》网络的过程中进行处理。
3. 《Arcane》网络
为了建立角色之间的网络,我们假设两个角色之间的互动强度由每个角色的档案提到另一个角色的次数来表示。因此,网络的节点是角色,节点之间通过基于每个角色的维基网站源引用其他角色维基的次数的强度不同的连接链接。
构建网络
在下面的代码块中,我们构建了边列表——包含每个连接的源节点和目标节点(角色),以及这两个角色之间的权重(共同引用频率)的连接列表。此外,为了有效地进行档案内搜索,我创建了一个names_ids
,它只包含每个角色的特定标识符,而不包括其余的网页地址。
# extract the name mentions from the html sources
# and build the list of edges in a dictionary
edges = {}
names_ids = {n : u.split('/')[-1] for n, u in names_urls.items()}
for fn in [fn for fn in os.listdir(folderout) if '.html' in fn]:
name = fn.split('.html')[0]
with open(folderout + '/' + fn) as myfile:
text = myfile.read()
soup = bs.BeautifulSoup(text,'lxml')
text = ' '.join([str(a) for a in soup.find_all('p')[2:]])
soup = bs.BeautifulSoup(text,'lxml')
for n, i in names_ids.items():
w = text.split('Image Gallery')[0].count('/' + i)
if w>0:
edge = '\t'.join(sorted([name, n]))
if edge not in edges:
edges[edge] = w
else:
edges[edge] += w
len(edges)
当这个代码块运行时,它应该返回大约 180 条边。
接下来,我们使用 NetworkX 图分析库将边列表转换为图对象,并输出图中的节点和边的数量:
# create the networkx graph from the dict of edges
import networkx as nx
G = nx.Graph()
for e, w in edges.items():
if w>0:
e1, e2 = e.split('\t')
G.add_edge(e1, e2, weight=w)
G.remove_edges_from(nx.selfloop_edges(G))
print('Number of nodes: ', G.number_of_nodes())
print('Number of edges: ', G.number_of_edges())
该代码块的输出:
该输出告诉我们,虽然我们从 67 个角色开始,其中 16 个角色最终没有与网络中的任何人建立连接,因此构建的图表中节点的数量较小。
可视化网络
一旦我们拥有网络,就可以将其可视化!首先,让我们使用 Matplotlib 和 NetworkX 内置工具创建一个简单的网络草图可视化。
# take a very brief look at the network
import matplotlib.pyplot as plt
f, ax = plt.subplots(1,1,figsize=(15,15))
nx.draw(G, ax=ax, with_labels=True)
plt.savefig('test.png')
该单元的输出图像:
虽然该网络已经提供了一些关于节目主要结构和最常见特点的线索,但我们可以使用开源网络可视化软件 Gephi 设计出更为详细的可视化。为此,我们首先需要将网络导出为 .gexf 图数据文件,如下所示。
nx.write_gexf(G, 'arcane_network.gexf')
现在,关于如何使用 Gephi 可视化此网络的教程:
YouTube 视频教程: www.youtube.com/watch?v=utm91FhZalQ
附加内容
这是一个扩展部分,我在视频中提到过。导出包含网络社区指数的节点表后,我使用 Pandas 读取该表,并为每个社区分配了不同的颜色。我从 ChatGPT 那里得到了这些颜色(及其十六进制代码),并要求它们与节目主色调对齐。然后,这段代码将颜色导出——我再次在 Gephi 中使用它来给最终图形上色。
import pandas as pd
nodes = pd.read_csv('nodes.csv')
pink = '#FF4081'
blue = '#00FFFF'
gold = '#FFD700'
silver = '#C0C0C0'
green = '#39FF14'
cmap = {0 : green,
1 : pink,
2 : gold,
3 : blue,
}
nodes['color'] = nodes.modularity_class.map(cmap)
nodes.set_index('Id')[['color']].to_csv('arcane_colors.csv')
总结
当我们根据发现的社区为网络着色时(社区指的是原始网络中的高度互联子图),我们揭示了四个主要群体,每个群体对应着故事情节中具体的一组角色。并不令人惊讶的是,算法将主角家庭与金克丝、维和范德(粉色)聚集在了一起。然后,我们还看到了扎恩地下人物(蓝色)的群体,如希尔科,而皮尔托弗的精英(蓝色)和军事执法(绿色)也被很好地分组在一起。
这种社区结构的美妙之处和作用在于,虽然这样的解释可以非常容易地将其置于背景中,但通常仅凭直觉很难得出类似的图示。尽管此处呈现的方法清楚地展示了我们如何利用网络科学提取虚拟(或真实)社交系统的隐藏联系,无论是律师事务所的合伙人、会计师事务所的同事,还是一家大型石油公司的 HR 部门。
工程师和数据专业人士提问的艺术
工程
在会议中提出有影响力问题的指导原则
·发布于 Towards Data Science ·阅读时间:5 分钟·2024 年 9 月 10 日
--
这张图片是作者通过 Meta AI 创作的。
作为工程师、数据科学家或数据分析师,我们经常需要在会议中提供反馈或提问,会议中可能会讨论新的想法、数据产品、机器学习模型等内容。尤其对于初级和中高级工程师和科学家来说,提出有思想性、建设性的问题能够提升讨论质量,并为项目带来更好的成果。
然而,并不是所有的问题都是一样的。提问过于基础、过于复杂或不必要的问题可能会破坏对话并减少你的可信度。那么,如何在正确的时间提出正确的问题呢?以下是四个指导原则,帮助你在会议中提出更好的问题。
1. 观察气氛
你是否曾经在会议中提问,后来因为低估了演讲者或听众的专业水平而后悔?或者你可能问了一个如此详细或复杂的问题,以至于没有人理解,让你觉得那个时候不应该深入探讨。别担心——你并不孤单!
提出好问题的一个最重要方面是理解听众是谁…
分块艺术:提升 RAG 架构中 AI 性能
高效的 AI 驱动检索的关键
·发表于Towards Data Science ·13 分钟阅读·2024 年 8 月 18 日
--
免费链接: 请像这样帮助我LinkedIn 帖子。
聪明的人都懒。 他们会找到解决复杂问题的最有效方法,在最小的努力下实现最大的结果。
在生成性 AI 应用中,这种高效性是通过分块实现的。就像将一本书分成章节让阅读更容易一样,分块将重要的文本分成较小、易于管理的部分,使其更易于处理和理解。
在探索分块的机制之前,首先需要了解这种技术所运作的更广泛框架:检索增强生成(Retrieval-Augmented Generation,RAG)。
什么是 RAG?
什么是检索增强生成(Retrieval Augmented Generation,RAG)
检索增强生成(RAG)是一种将检索机制与大语言模型(LLM 模型)结合的方法。它通过使用检索到的文档来增强 AI 能力,从而生成更准确和更具上下文丰富性的响应。
介绍分块
什么是分块
数据科学家的压力管理艺术
由 Johnson Wang 拍摄,来源于 Unsplash
你在不是数据科学家的时候所做的事情,可能会帮助你成为更好的数据科学家
·发布于 Towards Data Science ·10 分钟阅读·2024 年 5 月 25 日
--
数据科学家的工作可能非常繁忙。几周前,我经历了每个季度都会发生的最繁忙的一周。在这“规划周”期间,我需要使用最新的数据对四个季度、超过 10,000 个产品进行预测。
尽管所有模型已经在离线环境中预先训练过,但维护不同产品层级和复杂后处理程序的各种模型使得执行任务变得不容易,紧迫的截止日期几乎没有容错的空间。
这个视频记录了我在整个一周中的心理状态:
像许多在他处追寻梦想的人一样,我每周都会和妈妈视频聊天,聊聊近况,倾诉生活中的压力。每当我和她分享工作的压力时,她总是告诉我要心怀感激。
分词的艺术:为 AI 分解文本
揭开 NLP 的神秘面纱:从文本到嵌入
·发表于Towards Data Science ·10 分钟阅读·2024 年 9 月 26 日
--
由 Llama-3-8B 生成的分词示例。每个不同颜色的子词代表一个独立的标记(token)。
什么是分词(Tokenization)?
在计算机科学中,我们将像英语和普通话这样的语言称为“自然”语言。相反,像汇编语言和 LISP 这样的语言,专为与计算机交互而设计,被称为“机器”语言,遵循严格的句法规则,几乎没有解释的余地。尽管计算机擅长处理自身高度结构化的语言,但它们在处理人类语言的混乱性方面却表现得相当挣扎。
语言——尤其是文本——构成了我们大部分的沟通和知识存储。例如,互联网主要由文本组成。像ChatGPT、Claude和Llama这样的大型语言模型,都是通过使用复杂的计算技术,在海量的文本上进行训练——本质上是互联网上所有可用的文本。然而,计算机处理的是数字,而不是单词或句子。那么,我们如何弥合人类语言与机器理解之间的差距呢?
这就是自然语言处理(NLP)的作用所在。NLP 是一个结合语言学、计算机科学和人工智能的领域,旨在使计算机能够理解、解释和生成自然语言。无论是将文本从英语翻译成法语、总结文章,还是进行对话,NLP 都能让机器从文本输入中生成有意义的输出。
自然语言处理的第一步是将原始文本转化为计算机能够有效处理的格式。这个过程称为分词。分词是将文本分解成更小、易于处理的单位,称为词元,这些词元可以是单词、子词甚至是单个字符。以下是该过程的典型工作方式:
-
标准化: 在进行分词之前,文本需要进行标准化,以确保一致性。这可能包括将所有字母转换为小写、去除标点符号,并应用其他规范化技术。
-
分词: 然后,将标准化后的文本拆分成词元。例如,句子
“The quick brown fox jumps over the lazy dog”
可以被分词为以下词语:
["the", "quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog"]
- 数值表示: 由于计算机处理的是数值数据,每个词元会被转换为数值表示。这可以是简单地为每个词元分配一个唯一标识符,也可以是创建多维向量来捕捉词元的意义和上下文。
插图灵感来源于《Python 深度学习 作者:François Chollet](https://www.manning.com/books/deep-learning-with-python-second-edition)中的“图 11.1 从文本到向量”
分词不仅仅是拆分文本;它是以一种保留意义和上下文的方式准备语言数据,以便计算模型使用。不同的分词方法会显著影响模型理解和处理语言的效果。
在本文中,我们将重点讨论文本标准化和分词,探讨几种技术和实现方法。我们将为将文本转换为机器可以处理的数值形式打下基础——这是迈向更高级主题(如词嵌入和语言建模)的一项关键步骤,未来的文章中我们将深入讨论这些内容。
文本标准化
请看这两个句子:
1.
“dusk fell, i was gazing at the Sao Paulo skyline. Isnt urban life vibrant??”
2.
“Dusk fell; I gazed at the São Paulo skyline. Isn’t urban life vibrant?”
初看这些句子,它们传达了类似的意思。然而,当计算机处理这些句子时,尤其是在分词或编码任务中,由于微小的变化,它们可能表现出截然不同的结果:
-
大写化:
“dusk”
与“Dusk”
-
标点符号: 逗号与分号;问号的存在
-
缩写:
“Isnt”
与“Isn’t”
-
拼写和特殊字符:
“Sao Paulo”
与“São Paulo”
这些差异可能会显著影响算法如何解读文本。例如,“Isnt”
没有撇号,可能不会被识别为“is not”
的缩写,而像“São”
中的特殊字符“ã”
可能会被误解或导致编码问题。
文本标准化是自然语言处理中的一个关键预处理步骤,它解决了这些问题。通过标准化文本,我们减少了无关的变化,确保输入到模型中的数据是一致的。这个过程是一种特征工程方法,我们消除了对于当前任务没有意义的差异。
一种简单的文本标准化方法包括:
-
转为小写:减少因大小写不同而导致的差异。
-
去除标点符号:通过去除标点符号简化文本。
-
规范化特殊字符:将像
“ã”
这样的字符转换为其标准形式(如“a”
)。
将这些步骤应用到我们的句子中,我们得到:
1.
“dusk fell i was gazing at the sao paulo skyline isnt urban life vibrant”
2.
“dusk fell i gazed at the sao paulo skyline isnt urban life vibrant”
现在,句子更加统一,突出了只有在单词选择上的有意义差异(例如“was gazing at”
与“gazed at”
的区别)。
虽然有更高级的标准化技术,如词干提取(将单词还原为词根形式)和词形还原(将单词还原为词典形式),但这种基本方法有效地最小化了表面上的差异。
文本标准化的 Python 实现
下面是如何在 Python 中实现基本文本标准化的方法:
import re
import unicodedata
def standardize_text(text:str) -> str:
# Convert text to lowercase
text = text.lower()
# Normalize unicode characters to ASCII
text = unicodedata.normalize('NFKD', text).encode('ascii', 'ignore').decode('utf-8')
# Remove punctuation
text = re.sub(r'[^\w\s]', '', text)
# Remove extra whitespace
text = re.sub(r'\s+', ' ', text).strip()
return text
# Example sentences
sentence1 = "dusk fell, i was gazing at the Sao Paulo skyline. Isnt urban life vibrant??"
sentence2 = "Dusk fell; I gazed at the São Paulo skyline. Isn't urban life vibrant?"
# Standardize sentences
std_sentence1 = standardize_text(sentence1)
std_sentence2 = standardize_text(sentence2)
print(std_sentence1)
print(std_sentence2)
输出:
dusk fell i was gazing at the sao paulo skyline isnt urban life vibrant
dusk fell i gazed at the sao paulo skyline isnt urban life vibrant
通过标准化文本,我们已经最小化了可能会混淆计算模型的差异。模型现在可以专注于句子之间的变化,比如“was gazing at”
和“gazed at”
之间的区别,而不是像标点符号或大小写等差异。
标记化
在文本标准化后,自然语言处理中的下一个关键步骤是标记化。标记化涉及将标准化后的文本拆分成更小的单元,称为标记。这些标记是模型用来理解和生成自然语言的基本构建块。标记化为向量化做准备,其中每个标记都被转换为机器可以处理的数值表示。
我们的目标是将句子转换成计算机可以高效处理的形式。标记化有三种常见方法:
1. 单词级标记化
根据空格和标点符号将文本拆分成单独的单词。这是分解文本最直观的方式。
text = "dusk fell i gazed at the sao paulo skyline isnt urban life vibrant"
tokens = text.split()
print(tokens)
输出:
['dusk', 'fell', 'i', 'gazed', 'at', 'the', 'sao', 'paulo', 'skyline', 'isnt', 'urban', 'life', 'vibrant']
2. 字符级标记化
将文本拆分成单独的字符,包括字母,有时也包括标点符号。
text = "Dusk fell"
tokens = list(text)
print(tokens)
输出:
['D', 'u', 's', 'k', ' ', 'f', 'e', 'l', 'l']
3. 子词标记化
将单词拆分成更小的、有意义的子词单元。这种方法在字符级别的分词粒度与词汇级别分词的语义丰富性之间取得了平衡。像 字节对编码(BPE) 和 WordPiece 这样的算法属于这一类别。例如,BertTokenizer 将 “I have a new GPU!”
分词如下:
from transformers import BertTokenizer
text = "I have a new GPU!"
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
tokens = tokenizer.tokenize(text)
print(tokens)
输出:
['i', 'have', 'a', 'new', 'gp', '##u', '!']
在这里,“GPU”
被拆分为 “gp”
和 “##u”
,其中 “##”
表示 “u”
是前一个子词的延续。
子词分词提供了一种在词汇大小和语义表示之间的平衡方法。通过将稀有词拆解为常见的子词,它保持了可管理的词汇大小,而不牺牲含义。子词携带着有助于模型更有效理解上下文的语义信息。这意味着,模型可以通过将新词或稀有词分解成熟悉的子词来处理它们,从而提高其处理更广泛语言输入的能力。
例如,考虑单词 “annoyingly”
,它在训练语料库中可能比较稀有。它可以被拆解为子词 “annoying”
和 “ly”
。“annoying”
和 “ly”
在它们各自的形式中更为常见,并且它们的组合含义保留了 “annoyingly”
的本质。这种方法在粘着语(如土耳其语)中尤其有益,因为这些语言中的单词可以通过将子词组合在一起,形成极长的词来传达复杂的含义。
请注意,标准化步骤通常会集成到分词器本身。大型语言模型在处理文本时,使用标记作为输入和输出。以下是由 Llama-3–8B 在Tiktokenizer上生成的标记的可视化表示:
Tiktokenizer 示例使用 Llama-3–8B。每个标记都用不同的颜色表示。
此外,Hugging Face 提供了一份出色的分词器总结指南,我在本文中使用了其中的一些示例。
现在让我们探索不同的子词分词算法是如何工作的。请注意,所有这些分词算法都依赖于某种形式的训练,通常是在与对应模型训练相关的语料库上进行的。
字节对编码(BPE)
B字节对编码(Byte-Pair Encoding,BPE)是一种子词分词方法,最早由 Sennrich 等人在 2015 年的论文 《使用子词单元的稀有词神经机器翻译》 中提出。BPE 从一个包含所有唯一字符的基础词汇开始,逐步合并最频繁的符号对——这些符号可以是字符或字符序列——以形成新的子词。这个过程会持续进行,直到词汇表达到预定义的大小,这是你在训练前选择的超参数。
假设我们有以下单词及其频率:
-
“hug”
(出现 10 次) -
“pug”
(出现 5 次) -
“pun”
(出现 12 次) -
“bun”
(出现 4 次) -
“hugs”
(出现 5 次)
我们的初始基础词汇包含以下字符:[“h”, “u”, “g”, “p”, “n”, “b”, “s”]
。
我们将单词拆分为单个字符:
-
“h” “u” “g”
(hug) -
“p” “u” “g”
(pug) -
“p” “u” “n”
(pun) -
“b” “u” “n”
(bun) -
“h” “u” “g” “s”
(hugs)
接下来,我们计算每个符号对的频率:
-
“h u”
:出现 15 次(来自“hug”
和“hugs”
) -
“u g”
:出现 20 次(来自“hug”
,“pug”
,“hugs”
) -
“p u”
:出现 17 次(来自“pug”
,“pun”
) -
“u n”
:出现 16 次(来自“pun”
,“bun”
)
最频繁的字符对是 “u g”
(出现 20 次),因此我们将 “u”
和 “g”
合并为 “ug”
并更新我们的单词:
-
“h” “ug”
(hug) -
“p” “ug”
(pug) -
“p” “u” “n”
(pun) -
“b” “u” “n”
(bun) -
“h” “ug” “s”
(hugs)
我们继续进行此过程,合并下一个最频繁的符号对,例如将 “u n”
合并为 “un”
,直到达到我们期望的词汇大小。
BPE 通过指定合并操作的次数来控制词汇大小。频繁的单词保持不变,从而减少了大量记忆的需求。同时,罕见或未见过的单词可以通过已知子词的组合来表示。它被用于像 GPT 和 RoBERTa 这样的模型中。
Hugging Face 分词器库提供了一种快速且灵活的方法来训练和使用分词器,包括 BPE。
训练 BPE 分词器
下面是如何在一个示例数据集上训练 BPE 分词器的方法:
from tokenizers import Tokenizer
from tokenizers.models import BPE
from tokenizers.trainers import BpeTrainer
from tokenizers.pre_tokenizers import Whitespace
# Initialize a tokenizer
tokenizer = Tokenizer(BPE())
# Set the pre-tokenizer to split on whitespace
tokenizer.pre_tokenizer = Whitespace()
# Initialize a trainer with desired vocabulary size
trainer = BpeTrainer(vocab_size=1000, min_frequency=2, special_tokens=["[UNK]", "[CLS]", "[SEP]", "[PAD]", "[MASK]"])
# Files to train on
files = ["path/to/your/dataset.txt"]
# Train the tokenizer
tokenizer.train(files, trainer)
# Save the tokenizer
tokenizer.save("bpe-tokenizer.json")
使用训练好的 BPE 分词器:
from tokenizers import Tokenizer
# Load the tokenizer
tokenizer = Tokenizer.from_file("bpe-tokenizer.json")
# Encode a text input
encoded = tokenizer.encode("I have a new GPU!")
print("Tokens:", encoded.tokens)
print("IDs:", encoded.ids)
输出:
Tokens: ['I', 'have', 'a', 'new', 'GP', 'U', '!']
IDs: [12, 45, 7, 89, 342, 210, 5]
WordPiece
WordPiece 是另一种子词分词算法,由 Schuster 和 Nakajima 于 2012 年 提出,并由像 BERT 这样的模型广泛使用。与 BPE 相似,WordPiece 也从所有唯一字符开始,但在选择合并的符号对时有所不同。
下面是 WordPiece 的工作原理:
-
初始化:从包含所有唯一字符的词汇表开始。
-
预分词:将训练文本拆分为单词。
-
构建词汇表:通过迭代添加新的符号(子词)到词汇表中。
-
选择标准:与选择最常见的符号对不同,WordPiece 选择的符号对是将其加入词汇表后,最大化训练数据的可能性。
使用与之前相同的词频,WordPiece 评估哪个符号对在合并后能最有效地提高训练数据的概率。这比 BPE 基于频率的方法更具概率性。
类似于 BPE,我们可以使用tokenizers
库训练一个 WordPiece 分词器。
训练一个 WordPiece 分词器
from tokenizers import Tokenizer
from tokenizers.models import WordPiece
from tokenizers.trainers import WordPieceTrainer
from tokenizers.pre_tokenizers import Whitespace
# Initialize a tokenizer
tokenizer = Tokenizer(WordPiece(unk_token="[UNK]"))
# Set the pre-tokenizer
tokenizer.pre_tokenizer = Whitespace()
# Initialize a trainer
trainer = WordPieceTrainer(vocab_size=1000, min_frequency=2, special_tokens=["[UNK]", "[CLS]", "[SEP]", "[PAD]", "[MASK]"])
# Train the tokenizer
tokenizer.train(files, trainer)
# Save the tokenizer
tokenizer.save("wordpiece-tokenizer.json")
使用训练好的 WordPiece 分词器:
from tokenizers import Tokenizer
# Load the tokenizer
tokenizer = Tokenizer.from_file("wordpiece-tokenizer.json")
# Encode a text input
encoded = tokenizer.encode("I have a new GPU!")
print("Tokens:", encoded.tokens)
print("IDs:", encoded.ids)
输出:
Tokens: ['I', 'have', 'a', 'new', 'G', '##PU', '!']
IDs: [10, 34, 5, 78, 301, 502, 8]
结论
分词是自然语言处理中的基础步骤,旨在为计算模型准备文本数据。通过理解和实施适当的分词策略,我们使模型能够更有效地处理和生成自然语言,为像词嵌入和语言建模等高级主题打下基础。
本文中的所有代码也可以在我的 GitHub 仓库中找到:github.com/murilogustineli/nlp-medium
其他资源
除非另有说明,所有图片均由作者创建。
自动驾驶汽车背后的 AI 模型基础
学习如何使用 Python 中的 PyTorch 构建一个能够驾驶的神经网络
·发表于 Towards Data Science ·8 分钟阅读·2024 年 9 月 19 日
--
途中搭乘无人驾驶出租车(图像来源:作者)
我最近在旧金山体验了我的第一次无人驾驶出租车之旅。
我坐进后座,驾驶座是空的。
我目瞪口呆地看着汽车在停牌时发出右转的信号,并等待行人通过。然后,它慢慢加速,方向盘向右转动。
这真是一次非常平稳的旅程。不再需要担心把我的生命交给一个昏昏欲睡或脾气暴躁的出租车司机。
作为一名数据科学家,我对驱动自动驾驶车辆的技术感到着迷。所以,我学会了构建一个简单的神经网络,可以预测如何驾驶,我会带你一起了解这个过程。
控制车辆的基础知识
我们首先需要理解软件和硬件组件是如何协同工作的。
一辆车在水平面上行驶,可以朝四个不同的方向行驶。因此,这辆车配备了传感器,用来检测它在四个方向上与物体的距离:
-
与后方物体的距离,
-
与后方物体的距离,
-
与前方物体的距离,
AI 驱动的(向量)搜索基础
现代 AI 的兴起如何彻底改变了搜索应用…
·发布于Towards Data Science ·32 分钟阅读·2024 年 3 月 18 日
--
(照片由Tamanna Rumee提供,来自Unsplash)
最近,生成式 AI 的兴起和大型语言模型(LLMs)的出现让许多人开始思考搜索引擎的发展。基于对话的 LLM 是否会取代传统搜索引擎,或者这些模型易于产生幻觉的特性是否会使它们成为不可信的信息源?目前,这些问题的答案尚不明晰,但 AI 中心的搜索系统如you.com和perplexity.ai的快速普及表明,越来越多的人希望将现代语言模型的进展应用于搜索引擎。具有讽刺意味的是,我们多年来一直在搜索引擎中大量使用语言模型!BERT 的提出1使得我们在评估语义文本相似性方面取得了飞跃式的进步,导致这些语言模型被多种流行的搜索引擎采纳(包括 Google!)。在本概述中,我们将分析这些 AI 驱动的搜索系统的组成部分。
搜索引擎的基本组成部分
搜索引擎中的检索与排名(由作者创建)
偏差-方差权衡及其如何塑造今天的 LLM
低归纳偏差对于构建通用人工智能至关重要吗?
·发表于 Towards Data Science ·阅读时长 6 分钟·2024 年 11 月 2 日
--
图片由 BoliviaInteligente 提供,来源于 Unsplash
在今天的机器学习领域,我们发现自己被这些巨大的变换器模型包围,如chatGPT和BERT,它们在几乎所有下游任务中都表现出无与伦比的性能,但前提是需要先在上游任务上进行大量的预训练。那么,是什么让变换器需要如此多的参数,因此需要大量的训练数据才能发挥作用呢?
这是我想要深入探讨的问题,通过探索 LLM 和数据科学中偏差与方差这一基石主题之间的联系。应该会很有趣!
背景
首先,我们需要回顾一下,定义一些基础知识,为接下来的内容做铺垫。
方差
方差几乎可以与数据科学中的过拟合同义。这个术语的核心语言选择是“变化”的概念。高方差模型是指当输入变量 X 发生微小变化时,目标变量 Y 变化 非常大的模型。
因此,在高方差模型中,X 的微小变化会导致 Y 的巨大响应(这就是为什么 Y 通常被称为响应变量)。在经典的...
当前塑造 AI 未来的大问题
·发表于 Towards Data Science ·通过 Newsletter 发送 ·3 分钟阅读 ·2024 年 8 月 8 日
--
想写你的第一篇 TDS 文章吗?我们始终欢迎新作者的投稿。
模型发布、新工具和前沿研究的不断涌现,使得我们很难停下来花几分钟时间,思考一下 AI 的全局图景。从业者们正在努力回答的问题是什么——或者至少是需要关注的问题?所有这些创新对从事数据科学和机器学习的人员,以及这些不断发展的技术将来会影响的社区和社会,实际上意味着什么?
本周我们精选的文章从多个角度探讨了这些问题——从支撑(有时甚至推动)AI 热潮的商业模式,到模型能够和不能实现的核心目标。准备好进行一些引发深思的讨论了吗?让我们开始吧。
-
生成性 AI 的经济学
“我们应该期待什么,什么只是炒作?这项技术的承诺和实际情况之间有什么区别?”Stephanie Kirmer的最新文章直接而不妥协地探讨了 AI 产品的商业前景——这是一个及时的探索,考虑到(至少在某些圈子里)对于行业未来前景的悲观情绪日益加剧。
-
构建可靠 AI 应用的 LLM 三角原则
即使我们把人工智能驱动产品的经济因素放在一边,我们仍然需要应对实际构建这些产品的过程。Almog Baku的近期文章旨在为一个经常感觉混乱的生态系统增添结构性和清晰性;他从软件开发人员那里获得启示,最新的贡献聚焦于实践者在构建人工智能应用时应该遵循的核心产品设计原则。
图片来源:Teagan Ferraby于Unsplash
-
变压器架构能告诉我们什么?关于人工智能的讨论通常围绕着实用性、效率和规模展开。Stephanie Shen的最新文章聚焦于变压器架构的内部运作,提出了一个截然不同的研究方向:通过更好地理解人工智能系统中的复杂数学运算,我们可能获得有关人类认知和大脑的深刻见解。
-
为什么机器学习不适用于因果估计
随着任何突破性技术的到来,理解其不仅能做什么,还能做不到什么是至关重要的。Quentin Gallea, PhD在他的预测和因果推断入门文章中强调了这一区别的重要性,他深入剖析了为什么模型在前者方面如此擅长,而在后者方面仍然困难重重。
本周寻找其他值得探索的问题——无论是大的、中等的,还是非常集中的?我们邀请您探索一些我们最近的突出文章。
-
具有全面性和可操作性,Sachin Khandewal的首篇 TDS 文章展示了一种新的 RAG 方法,通过复杂推理提升输出质量。
-
自然语言处理与办公室结合,Maria Mouschoutzi, PhD的易懂教程,对角色的台词进行情感分析,旨在更好地理解这一技术的潜力(以及它的局限性)。
-
“如果有一种方法不仅能够将数据进行聚类,而且还能提供每个聚类的内在特征,岂不是很好?”Nakul Upadhya 分享了一个 适合初学者的可解释聚类入门。
-
在他最新的数学深入分析中,Reza Bagheri 提供了一个详细且精心插图的决策树和梯度提升算法的解析,它们是如何工作的,以及我们如何从零开始在 Python 中实现梯度提升算法。
-
如果你想进入数据科学领域,但没有通常能带来竞争力角色的资格证书,Mandy Liu 的新文章提供了所有你需要的灵感和可行的建议,帮助你将职业道路引向正确的方向。
-
神经网络如何感知分类变量及其层次结构?Valerie Carey 继续探索高基数分类特征及其处理的复杂性。
-
有兴趣解决复杂的优化问题吗?不要错过Will Fuks 的引人入胜的文章,介绍了一个利用线性规划简化全球范围内集装箱供应链操作的项目。
-
如果你更倾向于从产品的角度来处理机器学习模型,我们强烈推荐Julia Winn的精彩评估及其潜在影响概述,帮助你了解它们对用户体验的影响。
感谢你支持我们作者的工作!我们很喜欢发布新作者的文章,所以如果你最近写了一篇有趣的项目演示、教程或在我们核心话题上的理论反思,欢迎与我们分享。
直到下一期《Variable》,
TDS 团队
增强树的最大弱点
为什么分布漂移真的会伤害你的模型
·发布于Towards Data Science ·10 分钟阅读·2024 年 2 月 12 日
--
图片由Sebastian Unrau提供,来源于Unsplash
介绍
我已经做了五年数据科学家,在这五年中,我有机会参与了各种类型的无数项目。像许多数据科学家一样,在处理表格数据集时,我开始养成了一种反应模式:“如果是表格数据,特征工程 + 增强算法就能解决问题!”然后我就不再追问更多问题。
的确,增强算法在表格数据领域已经长时间处于技术前沿,以至于它们的优势变得难以质疑。
在本文中,我们将深入探讨增强算法背后的某些有趣的理论部分,特别是它们的核心组成部分——决策树,并理解在面对数据漂移时,使用增强算法时应该特别小心的场景。
通过线性回归的视角简要讨论数据漂移
数据漂移,简单来说,就是你的数据分布随时间变化,这可能会影响你的机器学习模型。
Blender 3D 点云可视化与渲染手册
Blender
完整指南:如何在 Blender 中创建大规模点云的 3D 体验
·发表于Towards Data Science ·阅读时长 20 分钟·2024 年 2 月 28 日
--
在本教程中,我希望填补一个巨大的互联网空白:如何利用其中一个最强大的 3D 工具来处理和可视化海量的点云数据集。
在 Blender 中对一个旧工厂进行的 3D 点云可视化。© F. Poux
这个工具叫做Blender。它允许我们通过尝试不同的数据可视化技术来解决复杂的分析场景。这正是将我们聚集在一起的原因。
如何在 Blender 的扩展数据可视化能力下,建立现实捕捉数据集(以点云形式)与 Blender 的最佳基础工作流?
🦊Florent:Reality Capture 是一个相对“新”的术语,可能会让人感到困惑,因为一些软件和公司正是以此命名。你可以将这个“学科”看作是“3D 制图”的一个专门分支,其目标是通过 LiDAR 或被动相机(通过摄影测量和 3D 计算机视觉)等各种传感器从现实世界中捕捉 3D 几何数据。你可以在本文中看到我们是如何做到的: 通过摄影测量进行 3D 重建指南
在本指南中,我将过程分解为九个清晰的步骤,如下所示。
《定制语言 AI 的商业指南》
解锁定制 LLM 解决方案的框架,你将能够理解
·发表于Towards Data Science ·阅读时间 8 分钟·2024 年 3 月 27 日
--
前言
本文说明了大型语言模型(LLM)是如何逐步适应定制化使用的。它旨在为没有计算机科学背景的人提供一种易于理解的类比,帮助他们了解 GPT 和类似的 AI 系统如何被定制。为什么要有艺术作品?请耐心一点,我希望你能享受这个过程。
引言
我不会从 ChatGPT、Claude 和生成性 AI 如何改变企业并将永远改变我们的生活、职业和商业的介绍开始这篇文章。关于这些内容已经有很多文章(尤其是 GPT 们自己写的…)。相反,今天我想关注的是,我们如何利用大型语言模型(LLM)来满足我们特定的、定制化的需求。
在我的职业生涯和私人生活中,我一直在帮助人们理解语言 AI 能够做什么以及不能做什么的基本概念,从为什么以及如何进行正确的提示(这超出了本文的范围)开始,到当管理者声称他们的公司拥有“自己的语言模型”时,这意味着什么。我觉得,特别是在将语言模型适应到特定业务需求的问题上,存在许多困惑和不确定性。到目前为止,我还没有遇到一个能够解决这一需求的成熟框架。
为了提供一个易于理解的解释,让非 IT 专业人士理解语言模型定制的可能性,我想出了一个类比,它让我回忆起早年作为酒吧钢琴演奏员时的经历。就像语言模型一样,酒吧钢琴演奏员常常被要求演奏各种歌曲,通常是没有明确要求或限定上下文的:“再来一首,Sam……”
认识 Sam——酒吧钢琴演奏员
想象一下你正坐在一家五星级酒店的钢琴酒吧里,那里有一架漂亮的三角钢琴。Sam(依然是人类)正演奏着钢琴曲。你正在享受着你的饮品,想知道 Sam 是否也能根据你的特定音乐口味演奏。为了便于论述,假设 Sam 实际上是一个语言模型,而你是酒店(或企业)老板,想知道 Sam 能为你做些什么。我在这里展示的“升级阶梯”是一个框架,提供了四个级别或方法,用以逐步调整 Sam 的知识和能力,以适应你的独特需求。在每个级别,需求变得越来越具体,同时让 Sam 适应所需的努力和成本也越来越高。
升级阶梯:从提示到训练你自己的语言模型
1. 提示:超越提问技巧
提示语言模型——模型的输出取决于提示的质量
你可以做的第一件事非常简单,但可能并不容易。你请求 Sam 播放你想听的歌曲。你越具体,也就是说,你的请求越清晰,你的措辞越准确(当然还取决于你在酒店酒吧喝了多少酒,你的发音),效果就越好。正如伏尔泰曾经说过:
“通过一个人的提问,而不是他的回答来判断他。”
“弹一些爵士乐”可能会,也可能不会使 Sam 演奏出你心中想要的旋律。“弹戴夫·布鲁贝克的《Take Five》的原版,右手弹奏萨克斯风的主旋律,左手保持节奏模式”则会得到一个非常具体的结果,前提是 Sam 接受了正确的训练。
你在这里所做的就是我对提示的类比——我们当前与像 GPT 或 Claude 这样的通用语言模型的交互方式。虽然提示是最直接的方式,但输出的质量在很大程度上依赖于你的提示的具体性和清晰度。正因为如此,提示工程成为了一个职业,而这个职业你可能在几年前都没听说过。
提示会带来巨大的差异,从得到一个糟糕、笼统,甚至错误的答案,到得到一个你可以实际使用的答案。这就是为什么在我日常使用 GPT 之类的工具时,我总是花一点时间思考一个合适的提示(我最喜欢的做法叫做“基于角色的提示”,即在提示中给模型一个特定角色,例如 IT 专家、数据工程师或职业教练。我们这里不深入探讨提示的细节,因为这超出了本文的范围)。
但是提示有其局限性:你可能不想总是通过提示来解释整个世界。在提示中提供所有的上下文并进行适当的书写可能是一项相当繁琐的任务(尽管基于聊天的语言模型在拼写上有一定的宽容度)。而且输出仍然可能偏离你的预期——在酒店酒吧场景中,不管你要求多么具体,你仍然可能对 Sam 演奏你最喜欢的歌曲不满意。
2. 嵌入或检索增强生成(RAG):提供与上下文相关的数据或指令
通过检索增强生成(RAG)将上下文嵌入或添加到你的提示中
你有一个想法。除了让 Sam “再演奏一次”(并具体提醒他你想听什么),你记得你的包里有乐谱。所以你把乐谱放在钢琴上,要求 Sam 演奏上面写的内容(前提是你给他一些激励,比如 10 美元现金)。
在我们的类比中,模型现在利用其固有的能力生成语言输出(Sam 演奏钢琴),并将这些能力应用于特定的上下文(Sam 演奏特定的歌曲)。
这种架构模式被称为检索增强生成(RAG),你为模型提供与你领域相关的附加上下文或参考材料。通过结合这些外部来源和数据,模型可以生成更有信息量和准确性的回答,量身定制以满足你的特定需求。从技术上讲,这涉及准备和清理文本上下文数据,然后将其转化为嵌入,并进行适当的索引。当提示时,模型会根据提示内容接收到与之相关的上下文数据。
这是更进一步的一步,因为它需要你付出一些努力(例如,给 Sam 10 美元),并且可能涉及一些实际的实施成本。
现在 Sam 演奏了你最喜欢的曲子——然而,你仍然对他的演奏方式不满意。某种程度上,你希望加点 Swing,或者少了某种感觉。所以你踏上了我们阶梯的下一步。
3. 微调:学习并适应反馈
通过不断提供(人类)反馈来微调模型——就像钢琴老师一样
这就是我的类比开始有些动摇的地方,特别是当我们在音乐语境中字面理解“调音”一词时。在这里,我们并不是在谈论调音萨姆的钢琴。相反,当我在这个语境中提到微调时,我是指花费大量时间与萨姆一起工作,直到他弹奏出我们希望的方式。所以我们基本上是在给他上钢琴课,提供反馈来改进他的演奏,并监督他的进展。
回到语言模型,这里的一种方法被称为强化学习(通过人类反馈)(RLHF),它很好地契合了我们对严格钢琴教师的类比。微调通过将模型的知识和技能调整(即调音)到特定任务或领域,进一步推进了定制化过程。同样,更技术一点来说,这里发生的事情基于强化学习,其核心是奖励函数。这个奖励会动态适应人类的反馈,这通常是人类对模型文本输出的 A/B 判断标签,基于相同的提示。
对于这个过程,我们需要大量的(计算)资源、大量精心挑选的数据和/或人工反馈。这也解释了为什么它已经排在我们提升阶梯的较高位置,但这还不是终点。
如果我们希望萨姆演奏或做一些非常具体的音乐事情呢?例如,我们希望他一起唱歌——这会让萨姆相当紧张(至少,曾经有过这样的请求让我有这种感觉),因为萨姆没有接受过训练,也从未尝试过唱歌……
4. 自定义模型训练:培养一位新的专家
从零开始训练模型来培养专家
在提升阶梯的顶端,我们遇到了自定义模型(预)训练,您实际上是从零开始创建一个全新的专家,完全按照您的要求量身定制。这也是我的类比可能会崩溃的地方(我可没说它是完美的!)——如何从头开始培养一位钢琴演奏者?不过我们还是继续这个类比——假设我们要训练萨曼莎,她一生中从未演奏过任何音乐,也没有唱过歌。所以我们将大量投资于她的教育和技能,把她送到那些音乐家学习我们希望他们演奏的顶级学府。
在这里,我们正在从零基础培养一种新的语言模型,为其注入在我们特定领域内执行所需的知识和数据。通过精心策划训练数据并调整模型及其架构,我们可以开发出一个高度专业化和优化的语言模型,能够应对即使是组织内最专有的任务。在这个过程中,目前的大型语言模型所训练的数据量和参数数量可能会相当惊人。例如,传闻称 OpenAI 最新的GPT-4 有 1.76 万亿个参数。因此,这种方法通常需要巨大的资源,对于今天的许多企业来说,往往是难以企及的。
结论
就像我们从害羞地要求 Sam 演奏 Dave Brubeck 的《Take Five》到开发新人才一样——随着我们逐步通过每个“升级阶梯”,所需的努力和资源显著增加,但我们在语言模型能力上的定制化和控制程度也随之增加。
当然,就像大多数框架一样,这个框架并不像我在这里展示的那样简单明了。可能存在混合或综合的方法,甚至是最精细的 RAG 实现,也需要你进行适当的提示。然而,通过理解并时常提醒自己这个框架,我相信你可以战略性地确定为特定使用场景所需的定制化程度。要释放语言 AI 的全部潜力,你需要在努力与成本之间找到合适的平衡,同时实现量身定制的表现。这也有助于在语言 AI 模型的适应和实施过程中,弥合商业与 IT 之间的沟通鸿沟。
我希望你们喜欢与 Sam 和 Samantha 见面,并适应他们在钢琴上的能力。我欢迎你在下方评论、批评或扩展你对这个类比的看法,或者简单地将这篇文章分享给可能从中受益的人。
*注释和参考文献:
本文的灵感来源于这篇关于检索增强生成技术的* 文章 *,由 Databricks 发布。
所有插图均由作者手工精心制作,充满自豪感 😃*
定制语言 AI 的商业指南 第二部分
向 ChatGPT 和其他基于聊天的语言 AI 发出提示——以及为什么您应该(不)关心它
·发表于Towards Data Science ·12 分钟阅读·2024 年 4 月 19 日
--
前言
本文阐明了如何“与”旨在进行对话交互的超大语言模型(LLM)进行沟通,如 ChatGPT、Claude 等,从而确保您从中获得的答案对于当前任务尽可能有用。人类与语言聊天机器人之间的这种沟通通常被称为提示。在本文中,我旨在为没有计算机科学背景的人提供关于该主题的简明概述,以便每个人都能理解。它也可以帮助企业理解在定制 LLM 过程中应当期待什么(或不应期待什么)。
提示是您在为企业定制语言模型时可以采取的四个步骤中的第一个。我在上一篇文章中介绍了四步框架,旨在解锁定制 LLM。如果您还没有阅读过,先读一下可能会对您有所帮助,这样您就能将本文中的思想放入更大的背景中。
解锁您将理解的定制 LLM 解决方案框架
towardsdatascience.com
引言
在 ChatGPT 广泛推出后不久,一个新的热门职业进入了 AI 领域:提示工程。这些 AI“耳语者”,即那些具有特定“提示”技能的人,能够与语言 AI 对话,让它以有用的方式回应,已成为炙手可热的职位(并且薪水丰厚)。考虑到正确提示的一个主要构建块仅仅是(或不那么简单地)提供准确的指令(见下文),我必须承认这一发展让我感到惊讶(尽管提示工程无疑不仅仅是“耳语”):精确简洁地沟通不是我们每个人都应具备的基本职业技能吗?但我又反思到,在软件开发中拥有精心设计的需求是多么重要,而“需求工程”角色已经成为成功软件开发项目的重要组成部分。
在 LLM 和提示的主题中,我观察到一种不确定性和“最佳猜测”,甚至是矛盾,这在我以往接触的任何 IT 相关主题中都没有经历过。这与 AI 模型的类型和规模以及它们的随机特性有关,而这些超出了本文的讨论范围。考虑到像 GPT-4 这样模型的1.76 万亿参数,从输入(你的“提示”)到输出(模型回应)的可能组合和路径数量几乎是无限且非确定性的。因此,应用程序主要将这些模型视为黑箱,而相关研究则侧重于经验方法,比如基准测试它们的性能。
不幸的是,我不能提供一个完美的、适用于所有情况的提示解决方案,来永远解决你的 LLM 需求。再者,不同的模型行为不同,你可能会理解我的困境。不过,也有一些好消息:一方面,你可以且应该始终考虑一些基本原则和概念,这些会帮助你优化与机器的互动。精心设计的提示能比糟糕的提示更进一步,这也是为什么深入探讨这个话题是非常值得的。另一方面,甚至可能根本不需要过多担心提示问题,这将节省你宝贵的计算时间(字面意义上的 CPU/GPU 时间,以及比喻意义上你自己大脑的时间)。
从“为什么”开始
这里我并不是指 Simon Sinek 的经典 TEDx 商业建议。相反,我鼓励你去好奇地思考技术为什么会这样做。我坚信,如果你至少理解一点软件的内在工作原理,它将极大地帮助你在应用中使用它。
那么,从原则上来说,输入(提示)是如何与输出(响应)相关的?为什么正确的提示会导致更合适的响应?要弄清楚这一点,我们至少需要对模型架构及其训练和微调有一个粗略的了解,而不需要理解像臭名昭著的 Transformer 架构和注意力机制等深奥的概念,这些概念最终促成了我们今天所知的 ChatGPT 类生成 AI 的突破。
从我们的角度来看,可以从两个方面进行考虑:
模型是如何 检索知识并生成响应的?与此密切相关
模型是如何被 训练和微调的?
重要的是要理解,LLM 本质上是一个深度神经网络,因此,它是基于统计和概率来工作的。简单来说,模型生成的输出反映了与上下文最接近的匹配,基于它从大量训练数据中学习到的知识。这里的一个构建块是所谓的嵌入,其中相似的词义(在数学上)彼此接近,尽管模型实际上并不“理解”这些词义。如果这听起来很花哨,它确实有点复杂,但同时,它“仅仅”是数学而已,所以不要害怕。
一个简单的词向量嵌入示例——相似的词“含义”彼此接近
在查看训练过程时,考虑语言模型所经过的训练数据和过程是有帮助的。模型不仅看过大量的文本数据,它还学会了什么构成了对特定问题的高质量回答,例如在像 StackOverflow 这样的站点上,或者在为模型训练和微调编写的高质量 Q&A 助手文档中。此外,在微调阶段,它还基于人类反馈学习并迭代地调整其最佳响应。如果没有这些强大的训练和微调工作,模型可能会像回答“你叫什么名字”这样的问题时,直接回答“你姓什么”,因为它在互联网上的表单中经常看到这种情况1。
我想表达的是:与自然语言 AI 交互时,始终要记住模型是如何学习的,以及它如何根据你的输入生成输出。尽管没有人能确切知道这一点,但考虑可能的相关性是很有用的:模型之前在哪些地方和什么上下文中可能见过与你相似的输入?在预训练阶段,模型有哪些数据可用,且数据的质量和数量如何?举个例子:有没有想过,为什么大型语言模型(LLM)能够解决数学方程(尽管不总是可靠,但有时仍然令人惊讶),而没有内建的计算能力?LLM 不进行计算,它们匹配模式!
提示入门 101
有许多提示技巧,以及大量的科学文献对其效果进行了基准测试。在这里,我只是想介绍一些知名的概念。我相信,一旦你理解了这些基本概念,你就能扩展自己的提示技巧库,甚至自己开发和测试新的技巧。
提问,它会给你答案
在深入具体的提示概念之前,我想强调一个我认为无法过度强调的普遍观点:
你的提示质量决定了模型的响应。
这里说的质量,我不一定指复杂的提示构建。我指的是提出精确问题或给出结构良好的指令并提供必要背景的基本思路。我在上一篇文章中提到过这个概念。当我们遇到萨姆——钢琴演奏者时,你会发现如果你让酒吧钢琴师演奏一首随机的爵士乐,他可能不会演奏你心中的那首曲子。而如果你准确地告诉他你想听的是什么,那么你对结果的满意度可能会更高。
类似地,如果你曾经有机会,假设雇人做些家务,而你的合同说明仅仅写着“浴室翻新”,你可能会感到惊讶,最后的浴室根本没有达到你预期的效果。承包商,就像模型一样,只会参考他学到的翻新和浴室装修的知识,并会按照这些经验来完成工作。
所以,以下是一些提示的通用指导原则:
· 要清晰具体。
· 要完整。
· 提供背景信息。
· 指定期望的输出风格、长度等。
这样,模型在生成响应时就能根据你的提示,拥有足够且匹配的参考数据。
角色扮演提示——简单,但被高估了
在 ChatGPT 的早期,角色扮演提示的想法随处可见:与其要求助手立即给出答案(即简单查询),你首先给它指定一个特定角色,如“教师”或“顾问”等。这样的提示可能看起来像这样2:
从现在起,你是一位优秀的数学老师,总是正确地教授你的学生数学问题。我是你的学生之一。
已经证明,这一概念能够带来更优的结果。一个研究论文报告指出,通过这种角色扮演,模型会隐式地触发逐步推理过程,这正是你希望它在应用 CoT 技巧时所做的,如下所示。然而,这种方法也已被证明有时会表现不尽如人意,因此需要精心设计。
根据我的经验,单纯分配一个角色并不能奏效。我曾尝试过上面提到的论文中的示例任务。与这项研究不同,GPT3.5(截至今天是 OpenAI 的 ChatGPT 的免费版本,你可以自己尝试)通过简单查询得出了正确的结果:
一个使用简单查询的示例,而不是2建议的角色扮演提示,依然得出了正确的回答。
我还尝试过用简单查询和角色扮演进行不同的逻辑挑战,使用了类似上面的提示。在我的实验中,发生了两种情况:
要么 简单查询在第一次尝试时给出了正确答案,要么
无论是 简单查询还是角色扮演都得出了错误的,但不同的答案
角色扮演在我的任何简单(非科学严谨)实验中并未超越查询。因此,我得出结论,模型最近一定有所改进,而角色扮演提示的影响正在减弱。
通过查看不同的研究,在没有进行广泛的进一步实验的情况下,我认为为了超越简单查询,角色扮演提示需要嵌入到合理且深思熟虑的设计中,以超越最基本的方法——否则完全没有价值。
我很高兴在下面的评论中看到你们在这方面的经验。
少量样本(Few-Shot)即上下文学习
另一个直观且相对简单的概念是所谓的少量样本提示,也称为上下文学习。与零样本提示不同,我们不仅要求模型执行某项任务并期望其给出结果,还额外提供(“少量”)解决方案的示例。尽管你可能认为提供示例会带来更好的表现,但这实际上是一项非常显著的能力:这些大语言模型能够进行上下文学习,即通过仅凭推理,在少量输入-标签对的条件下执行新任务,并为新输入做出预测3。
设置少量样本提示包括:
(1) 收集所需回答的示例,并且
(2) 编写你的提示,并* 指示如何处理这些示例。
让我们来看一个典型的分类示例。这里,模型被给出几个判断为正面、负面或中立的陈述。模型的任务是对最终的陈述进行评分:
一个典型的少量样本提示的分类示例。模型需要将陈述分类为给定的类别(正面/负面)。
再次强调,尽管这是一种简单直观的方法,我对它在最先进的语言模型中的价值持怀疑态度。在我的(再次,非科学严谨的)实验中,少样本(Few-Shot)提示在任何情况下都没有优于零样本(Zero-Shot)。(模型已经知道,不守时的鼓手是一种糟糕的体验,而我并没有教它这个……)。我的发现似乎与最近的研究一致,甚至有研究显示相反的效果(零样本优于少样本)4。
在我看来,根据这一经验背景,值得考虑的是,这种方法的设计成本、计算成本、API 成本和延迟成本是否值得投资。
CoT 提示或“让我们一步步思考”
思维链(CoT)提示旨在让我们的模型更擅长解决复杂的多步骤推理问题。它可以像在输入查询中添加 CoT 指令“让我们一步步思考”一样简单,从而显著提高准确性[5][6]。
与其像少样本(Few-Shot)方法那样仅提供最终查询或在提示中添加一个或几个示例,不如提示模型将其推理过程分解成一系列中间步骤。这类似于人类如何(理想情况下)解决一个具有挑战性的问题。
记得你在学校的数学考试吗?通常,在更高级的课程中,你不仅被要求解答一个数学方程式,还要写下你是如何推导出最终解答的逻辑步骤。即使答案不对,你可能也会因为数学上合理的解题步骤而获得一些分数。就像你在学校的老师一样,你期望模型将任务分解成子任务,进行中间推理,并得出最终答案。
再次强调,我自己也进行过不少 CoT 实验。并且,大多数时候,仅仅添加“让我们一步步思考”并没有改善回答的质量。事实上,似乎CoT 方法已经成为最近经过微调的基于聊天的语言模型(如 ChatGPT)的隐式标准,即使没有明确的指令,回应也经常被分解为推理的块。
然而,我遇到过一个例子,在这个例子中,明确的 CoT 指令确实显著改善了答案。我使用了来自这篇文章的 CoT 示例,但将其改编成了一个 trick question。在这里,你可以看到 ChatGPT 是如何陷入我的陷阱的,当它没有明确要求使用 CoT 方法时(尽管回应中展示了逐步推理):
这是一个通过简单查询而不是 CoT 提示提出的 trick question。尽管回答被“逐步”分解,但它并不完全正确。
当我在相同的提示中添加“让我们一步步思考”时,它正确地解答了这个 trick question(嗯,它实际上是无法解决的,ChatGPT 也正确指出了这一点):
使用明确的思维链提示,得到正确回答的同一个陷阱问题
总结来说,思维链提示(Chain of Thought prompting)旨在建立推理能力,而这些能力对于语言模型来说是很难隐性地获取的。它鼓励模型阐述并完善其推理过程,而不是试图直接从问题跳跃到答案。
再次强调,我的实验结果揭示了简单的思维链方法的效益有限(添加“让我们一步步思考”)。思维链在某些情况下确实比简单查询表现更好,同时添加思维链指令的额外努力也很少。这种成本效益比是我喜欢这种方法的原因之一。另一个原因是,个人而言,我喜欢这种方法,它不仅能帮助模型,还能帮助我们人类进行反思,甚至在构建提示时,逐步考虑必要的推理步骤。
如前所述,当模型变得越来越精细调整并习惯于这种推理过程时,这种简单的思维链方法的效果可能会逐渐减弱。
结论
在本文中,我们深入探讨了基于聊天的大型语言模型的提示方法。与其仅仅向你介绍最流行的提示技术,我更鼓励你从“为什么提示如此重要”的问题开始这段旅程。在这段旅程中,我们发现,随着模型的演变,提示的重要性正在逐渐减弱。目前不断发展的模型架构可能会进一步减少提示技巧的相关性。基于代理的框架就是其中之一,它通过在处理特定查询和任务时采取不同的“路线”来实现。
然而,这并不意味着在提示中清晰、具体并提供必要的上下文就不值得付出努力。相反,我是这方面的坚定倡导者,因为这不仅能帮助模型,还能帮助你自己弄清楚你究竟想要达到什么目的。
就像在人类交流中一样,多个因素决定了达成预期结果的合适方法。通常,结合并反复尝试不同的方法,能在特定情境下获得最佳效果。尝试、测试、迭代!
最后,与人类交互不同,你几乎可以无限次地在个人的试验和错误提示旅程中进行测试。享受这段旅程吧!
*笔记与参考资料
所有插图均由作者亲手精心绘制 😃*
1: 大型语言模型如何工作:从零到 ChatGPT
medium.com/data-science-at-microsoft/how-large-language-models-work-91c362f5b78f
dl.acm.org/doi/abs/10.1145/3411763.3451760
。
5: 什么时候需要为 ChatGPT 提供链式思维提示?
[6]: 大型语言模型是零-shot 推理者
反对集中式奖章架构的案例
为什么定制化、去中心化的数据质量优于奖章架构
·发布于Towards Data Science ·8 分钟阅读·2024 年 12 月 9 日
--
DALL-E 生成
我见过太多文章赞扬奖章架构是解决企业数据质量问题的最佳方案。乍一看,这种结构化的三层方法听起来是显而易见的——将数据组织成简洁的铜层、银层和金层,便建立了一个完美的数据质量提升方案。
但仔细观察后,我对这种架构方法的反感愈发强烈。的确,它承诺提供一致的、可扩展的和集中的信息质量提升。然而,在实际操作中,质量问题总是被过晚且僵化地用相同的工具进行修正,无论其背景如何。
企业是复杂的自适应系统,拥有截然不同的数据源,每个数据源在信息质量方面面临独特的挑战。为什么要对它们强制实行相同的僵化流程呢?将所有数据源强行纳入相同的集中的质量框架只会导致低效和不必要的开销。
我想挑战奖章架构作为企业数据质量问题的最佳解决方案。我将为一种更定制化、去中心化的方法提出理由——这一方法灵感来源于全面质量管理(TQM),并与之相一致……
检索和评估 RAG 相关上下文的挑战
一个案例研究,展示如何使用 Ragas、TruLens 和 DeepEval 衡量你在检索增强生成系统中上下文相关性的一年级文本理解练习
·发表于Towards Data Science ·阅读时间 13 分钟·2024 年 6 月 10 日
--
一年级阅读理解练习,作为检索增强生成(RAG)中的上下文相关性示例
你检索到的上下文与用户输入的相关性,在你的检索增强生成(RAG)管道的表现中起着关键作用。然而,检索相关上下文本身也面临着一系列挑战。更具挑战性的问题是如何有效地衡量上下文相关性。
本文将通过以下一年级文本理解示例,探讨检索相关上下文和衡量上下文相关性的挑战。
text = """Lisa is at the park. Her dog Bella, is with her.
Lisa rides her bike and plays with Bella. They race each other in the sun.
Then Lisa goes to the pond to see the ducks.
She thinks they are so cute and funny.
"""
questions = [
"Where is Lisa?",
"What is Lisa's dog's name?",
"What does Lisa do in the park?",
"Why does Lisa go to the pond?"
]
请注意,像gpt-3.5-turbo
这样的最先进 LLM,如果将整个文本输入进去,轻松就能回答这个一年级学生的练习题……
置换检验的多彩力量
你不需要了解数学就能学习这个强大的统计检验。如果你只打算学习一种统计检验,那应该是这个。
·发表于 Towards Data Science ·阅读时间:7 分钟·2024 年 5 月 16 日
--
作者提供的鸢尾花图,由 Midjourney 创建
机器学习和统计学常常因其复杂的数学基础而让人感到畏惧。然而,有些概念,如置换检验,却出奇地直观,并且可以通过简单的实验来理解。置换检验是评估不同领域结果显著性的重要工具,从心理学到数据科学均有应用。让我们通过一个生动的例子来探讨这一强大概念:学习德语中颜色的名称。在这里,你是机器学习(ML),一个复杂的神经网络,唯一通过自然方式——生育一个孩子——创造出来的!
颜色学习实验
德语中的颜色,原始数据集。图片由作者提供。
假设我给你一个数据集,里面用相同颜色的墨水标注每个颜色的名称。你研究它几分钟,记住这些颜色名称,然后我测试你。令人印象深刻的是,你几乎准确地说出了所有颜色的名称,只有一个小小的错误。
生成性 AI 的即将版权审判
法院正准备裁定生成性 AI 是否侵犯版权——让我们讨论一下这到底意味着什么
·发布于数据科学前沿 ·14 分钟阅读·2024 年 4 月 1 日
--
图片由Annelies Geneyn拍摄,来源于Unsplash
美国的版权法是一个复杂的领域。我们这些非律师的人,理所当然地很难搞清楚它到底意味着什么,以及它保护了什么,没保护什么。数据科学家通常不会花很多时间考虑版权问题,除非我们正在为开源项目选择许可证。即使是这样,有时候我们也会跳过那一部分,没太处理它,尽管我们知道应该处理。但是法律界现在开始密切关注版权与生成性 AI 的交集,这可能对我们的工作产生实际影响。在我们讨论它如何影响生成性 AI 领域之前,让我们先回顾一下版权的真相。
版权
-
美国的版权法与所谓的“原创作品”相关。这些包括以下类别的作品:文学;音乐;戏剧;哑剧和舞蹈作品;图像、图形和雕塑作品;视听作品;声音录制;衍生作品;编纂作品;建筑作品。
-
内容必须以可版权化的形式进行创作或记录。“创意是不能获得版权的。只有具体的表达形式(例如书籍、剧本、画作、电影或照片等)才能获得版权。一旦你将创意以固定的形式表达出来——比如数字绘画、录制的歌曲,甚至是在餐巾纸上的涂写——如果它是原创作品,它会自动获得版权。” — 电子前沿基金会
-
版权保护意味着只有版权持有者(作者或创作者、继承其权利的后代,或权利的购买者)才能做以下事情:制作并销售作品的复制品、从原作品创作衍生作品、以及公开表演或展示作品。
-
版权不是永恒的,它在一定时间后会结束。通常来说,这是在作者去世后 70 年或内容发布后 95 年。(在美国,1929 年之前的作品通常处于“公有领域”,这意味着它不再受到版权保护。)
为什么版权存在?近期的法律解释认为,版权的存在不仅仅是让创作者致富,而是鼓励创作,使我们拥有一个充满艺术和文化创造力的社会。基本上,我们与创作者交换金钱,激励他们为我们创造伟大的作品。这意味着很多法院在审理版权案件时会问:“这个复制品是否有助于创造一个具有创意、艺术性和创新的社会?”并且在做出判断时也会考虑这一点。
合理使用
此外,“合理使用”并不是可以忽视版权的免死金牌。判断某一内容使用是否为“合理使用”有四个标准:
-
第二次使用的目的和性质:你是在用这些内容做一些创新和不同的事情,还是仅仅是在复制原作?你的新作品本身是否具有创新性?如果是,那么它更有可能被认为是合理使用。此外,如果你的使用目的是为了盈利,那么它就不太可能被认为是合理使用。
-
原作品的性质:如果原作品具有创意性,那么通过合理使用破坏版权会更困难。如果它只是事实,那么你更有可能适用合理使用(比如引用研究文章或百科全书)。
-
使用的数量:你是在复制全部内容吗?还是仅仅是复制一段或一小部分?合理使用时,尽量使用必要的最小量是很重要的,尽管有时你可能需要使用大量内容来创作衍生作品。
-
影响:你是在从原创者那里窃取客户吗?人们会购买或使用你的复制品,而不是购买原版吗?创作者会因为你的复制品失去收入或市场份额吗?如果是,那么它可能不是合理使用。(即使你没有赚钱,这一点仍然相关。)
你必须满足所有这些测试标准,才能认为是合理使用,而不仅仅是其中一两项。当然,所有这些都需要法律解释。(本文不是法律建议!)但现在,在我们掌握了这些事实之后,让我们思考生成性人工智能的作用以及上述概念为何会与生成性人工智能发生碰撞。
生成性人工智能回顾
我的专栏的常读者应该已经对生成性人工智能的训练过程有了相当清晰的理解,但让我们做一个非常简短的回顾。
-
收集大量数据,并通过分析数据中的模式来训练模型。(正如我之前所写:“有报告显示,GPT-4 的训练数据大约有1 万亿个单词。这些单词每一个都由人类创作,源于他们自己的创造能力。为了让大家更好理解,‘权力的游戏’第一部书大约有 292,727 个单词。因此,GPT-4 的训练数据大约相当于3,416,152 本该书。”)
-
当模型学习到数据中的模式(对于大型语言模型(LLM),它学习的是语言语义、语法、词汇和习语等内容)时,它将通过人工微调,以确保在人们与之互动时,模型能按照预期的方式表现。这些数据中的模式可能非常具体,以至于一些学者认为模型可以“记住”训练数据。
-
然后,模型将能够根据它学到的模式回答用户的提示(对于大型语言模型(LLM),即用非常像人类的语言回答问题)。
这些模型的输入(训练数据)和输出对版权法有重要影响,所以我们来仔细分析一下。
训练数据与模型输出
训练数据对于创建生成性人工智能模型至关重要。目标是教会模型复制人类的创造力,因此模型需要看到海量的人类创造性作品,以便学习这些作品的样貌和声音。但正如我们之前了解到的,人类创作的作品归创作者所有(即使它们只是在纸 napkin 上随便写的)。为每个创作者支付作品的版权费用对于我们训练即使是一个小型生成性人工智能模型所需的海量数据来说是不可行的。那么,是否可以视为合理使用,将他人的作品输入训练数据集并创建生成性人工智能模型呢?让我们来看一下合理使用的测试标准,看看我们最终的结论是什么。
- 第二种用途的目的和特征
我们可以争论,使用数据来训练模型并不算真正的创造衍生作品。例如,这与用一本书或一首音乐来教孩子有什么不同吗?反驳意见有两个:首先,教一个孩子与使用成千上万本书来生成一个有利润的产品不同;其次,生成型 AI 能够如此精准地复制它所训练的内容,基本上是一个复制几乎逐字不差的工作的高级工具。生成型 AI 的结果有时是否具有创新性,并且完全不同于输入内容?如果是,那可能是由于非常有创意的提示工程,但这是否意味着底层工具是合法的?
然而,从哲学角度来看,机器学习试图尽可能准确地再现其从训练数据中学到的模式。它从原始作品中学到的模式是否与原始作品的“核心”相同?
2. 原始作品的性质
这在不同种类的生成型 AI 之间差异很大,但由于训练任何模型所需的数据量庞大,因此至少有一些数据可能符合创造力的法律标准。在许多情况下,使用人类内容作为训练数据的主要原因是为了将创新的(高度多样化的)输入引入模型。除非有人要逐一审查 GPT-4 的 1 万亿单词,并决定哪些是有创造力的,哪些不是,否则我认为这一标准不符合合理使用的要求。
3. 使用的数量
这与第 2 点有些相似。因为,几乎可以说生成型 AI 的训练数据集使用了它能够获取的所有内容,而且数据量需要庞大且全面;根本没有所谓的“最小必要”内容量。
4. 影响
最后,影响问题是生成型 AI 的一个大难点。我想我们都知道有些人时不时地使用 ChatGPT 或类似工具,而不是在百科全书或报纸中寻找答案。有强烈的证据表明,人们使用像 Dall-E 这样的服务来请求“以[艺术家姓名]的风格”创作视觉作品,尽管这些服务显然已经做出了一些努力来阻止这种行为。如果问题是人们是否会使用生成型 AI 而不是支付原始创作者报酬,那显然在某些行业中确实有这种情况。而且我们可以看到,像微软、谷歌、Meta 和 OpenAI 这样的公司通过生成型 AI 获得了数十亿美元的估值和收入,所以他们显然不会在这个问题上轻松过关。
计算中的复制概念
我想暂停一下,讨论一个虽不直接相关但非常重要的问题。版权法并没有很好地应对计算机技术,特别是软件和数字化作品的问题。版权法大多是在一个更早的时代制定的,当时复制一张黑胶唱片或重新出版一本书是一项专业化且昂贵的任务。但如今,任何计算机上的东西基本上都可以通过点击鼠标在几秒钟内复制,复制的概念与以前大不相同。而且,请记住,安装任何软件都算作复制。数字复制在我们文化中意味着的内容与以前计算机出现之前的复制大不相同。关于版权在数字时代如何运作的问题有许多重要的质疑,因为很多内容似乎不再那么相关。你是否曾从 GitHub 或 StackOverflow 上复制过一段代码?我当然有!你是否仔细审查了内容许可证,确保它适用于你的使用场景?你应该这么做,但你做了吗?
《纽约时报》诉 OpenAI
现在我们对这个困境有了一个大致的了解,创作者和法律是如何处理这一问题的呢?我认为最有趣的案例之一(当然有很多)是《纽约时报》提起的案件,因为它在某种程度上探讨了复制的含义,而我认为其他案件未能做到这一点。
正如我上面提到的,复制数字文件的行为是如此普遍且正常,以至于很难想象执行复制一个数字文件(至少没有意图将该文件精确分发给全球公众,从而违反其他合理使用测试)会构成版权侵权。我认为这就是我们在生成型 AI 问题上需要关注的地方——不仅仅是复制,而是对文化和市场的影响。
生成型 AI 真的在复制内容吗?例如,训练数据输入,训练数据输出?《纽约时报》在其文件中显示,经过非常具体的提示,你可以从 ChatGPT 获取《纽约时报》文章的逐字文本。由于《纽约时报》有付费墙,如果这一点属实,那么这似乎明显违反了合理使用的效果测试。到目前为止,OpenAI 的回应是“嗯,你使用了许多复杂的提示来获得这些逐字结果”,这让我想知道,他们的论点是否是,如果生成型 AI 有时 生成它训练时使用的内容的逐字副本,那就不违法?(环球音乐集团提出了一个类似的案件,涉及音乐,认为生成型 AI 模型 Claude 可以几乎逐字地复制版权歌曲的歌词。)
我们要求法院裁定,使用版权材料的多少以及何种用途是可以接受的,在这个背景下这将是一个挑战——我倾向于认为,使用数据进行训练本身不应是问题,但重要的问题是模型如何被使用,以及这会带来什么样的影响。
我们通常把合理使用看作是一个单一的步骤,比如在文章中引用一段文字并注明来源。我们的系统有一套法律思想体系,已经为这种情况做好了充分的准备。但在生成式人工智能中,它更像是两个步骤。要说侵犯了版权,我认为,如果内容在训练中被使用,那么它也必须能够从最终模型中被提取,并且以某种方式抢占原始材料的市场。我不认为你能将使用的输入内容的数量与能被逐字提取出来的输出内容的数量分开来看。那么,ChatGPT 真的符合这一点吗?我们将看到法院是怎么认为的。
Ars Technica、The Verge、TechDirt
DMCA
这些问题还有另一个有趣的角度,那就是 DMCA(数字千年版权法案)是否在这里具有相关性。你可能对这部法律有所了解,因为几十年来它一直被用来迫使社交媒体平台移除未经版权持有者授权发布的音乐和电影文件。这项法律的基础思想是,你可以通过类似“打地鼠”游戏的方式对付版权侵权者,一次移除一件内容。然而,当涉及到训练数据集时,这显然行不通——你需要重新训练整个模型,在大多数生成式人工智能的情况下,代价是巨大的,需要从训练数据中移除相关文件。理论上,你仍然可以使用 DMCA 来强制移除侵犯版权模型生成的内容,但证明是哪一个模型生成了该内容将是一个挑战。不过,这并没有解决我描述的输入+输出作为侵权的关键问题。
权力问题
如果这些行为实际上侵犯了版权,法院仍然需要决定如何处理此事。很多人争辩说,生成式人工智能在某种意义上是“太大而不能倒”的——他们不能废除那些让我们走到今天的做法,因为大家都喜欢 ChatGPT,对吧?我们被告知,生成式人工智能将会彻底改变[插入行业名称]!
尽管是否侵犯版权的问题仍然待定,但我确实觉得如果侵犯了版权应该有相应的后果。我们应该在什么时刻停止宽容那些规避法律或直接违反法律的有权势的人和机构,假设他们认为请求原谅比获得许可更容易?这个问题并不完全明确。没有一些人以这种方式行事,我们今天所依赖的许多创新将无法出现,但这并不一定意味着这样做值得。放任这些情况通过是否会导致法治的贬值?
像现在许多听众一样,我在读罗伯特·卡罗的《权力经纪人》。听到关于罗伯特·摩西如何在 20 世纪初处理纽约法律问题的故事很吸引人,因为他处理分区法的方式似乎让人联想到优步在 2010 年代初期如何处理旧金山租车司机的法律问题,以及现在那些开发生成性 AI 的大公司如何应对版权问题。与其遵守法律,他们采取了这样一种态度:法律的约束不适用于他们,因为他们正在构建的东西如此重要和有价值。
然而,我并不完全相信这是真的。每个案例在某些方面都是独特的,但一个强大的人决定他认为好的想法无可避免地比任何人想法更重要,这让我感到不舒服。生成性 AI 可能是有用的,但认为它比拥有一个充满活力和创造力的文化社会更重要,这种观点让我感到不真诚。法院仍然需要决定生成性 AI 是否对艺术家和创作者产生了寒蝉效应,但这些创作者提起的诉讼认为确实如此。
未来
美国版权局并没有忽视这些棘手的问题,尽管它们可能有点迟到,但他们已经发布了一个关于其生成性 AI 相关内容的最新博客文章。然而,这篇文章在具体细节上非常简短,仅告诉我们相关报告将在未来发布。这个部门的工作将专注于以下三个领域:
-
“数字复制品”:基本上是人类的深度伪造(deepfakes)和数字双胞胎(digital twins)(比如特技替身和演员在工作时需要被扫描,以便可以被数字化模仿)
-
“包含 AI 生成内容的作品的版权资格”
-
“在版权作品上训练 AI 模型”
这些都是重要的话题,我希望结果能够引人深思。(一旦这些报告发布,我会写关于它们的文章。)我希望参与这项工作的政策制定者能够充分了解相关问题并具备技术能力,因为一个官僚很容易通过不明智的新规则让整个局面变得更糟。
另一个未来的可能性是,伦理数据集将被开发用于训练。这已经是 HuggingFace 的一些人通过名为 The Stack 的代码数据集所做的事情。我们能否为其他形式的内容做类似的事情?
结论
然而,无论政府或行业提出什么方案,法院都在继续处理这个问题。如果法院中的某个案件由生成式 AI 一方败诉,会发生什么?
这至少可能意味着,生成式 AI 创造的一些收益将会回馈给创作者。我并不完全相信生成式 AI 的整个概念会消失,尽管我们确实在 Napster 时代见证了许多公司的倒闭。法院可能会使那些生成式 AI 公司破产,和/或禁止生成式 AI 模型的生产——这并非不可能!然而,我认为这并不是最可能的结果——相反,我认为我们将会看到一些处罚以及法律上的碎片化(这个模型可以,那个模型不行,等等),这可能并不会让局势在法律上变得更加明晰。
我真的希望法院能够解决一个问题:何时以及如何应当认为生成式 AI 模型侵犯了版权,而不是将输入和输出问题分开,而是将它们作为一个整体来审视,因为我认为这对理解整个局势至关重要。如果他们这样做了,我们可能能够提出适用于我们正在处理的新技术的法律框架。如果没有,我担心我们会陷入一个法律泥潭,法律无法有效引导我们的数字创新。我们需要更符合数字时代的版权法。但我们同样需要智能地保护人类的艺术、科学和创造力,我不认为 AI 生成的内容值得用来换取这些保护。
在我的网站上阅读更多内容 www.stephaniekirmer.com.
参考文献与进一步阅读
欢迎来到这个关于数字时代法律权利和责任的误解的讨论。这是…
www.eff.org [## 展望未来:2024 年美国版权局的 AI 计划 | 版权
本文预览了美国版权局全面审查版权法的下一步举措……
blogs.loc.gov [## 关于 AI 版权的可怕真相是,没人知道接下来会发生什么
生成型 AI 模型自 2022 年起快速发展。它们能够生成代码、文本、艺术作品等。但是,存在严重的……
www.theverge.com [## AI 是否会摧毁 DMCA 版权妥协?- Frost Brown Todd | 全方位服务律师事务所
在 1990 年代,和今天一样,互联网依赖内容,这意味着它既有问题,又是问题本身。它有一个问题……
frostbrowntodd.com [## 生成型 AI 正在挑战一部 234 年历史的法律
这项技术可能终于将版权推向了临界点,颠覆了拥有创造性社会的意义……
www.theatlantic.com [## 版权法如何在 2024 年威胁 AI 行业
如果 2023 年是人工智能改变一切的年份,那么 2024 年可能会被记为美国版权法的重大转折点……
www.reuters.com [## 人工智能版权诉讼如何让整个行业面临灭绝风险
人工智能公司正面临关于其公正使用声明的重大版权挑战,整个行业的未来悬而未决……
www.theverge.com [## 当前针对生成式 AI 的法律案件仅仅是个开始 | TechCrunch
像 ChatGPT 和 DALL-E 2 这样的生成式人工智能已经进入主流,并吸引了投资者的关注。但是它们也面临着…
techcrunch.com [## The Intercept 为数字出版商起诉 OpenAI 制定新的法律策略
针对 OpenAI 的两起诉讼正在为 AI 开发者的版权诉讼开辟一条新路径——这条路径专门针对…
www.niemanlab.org [## Patronus AI | 推出 CopyrightCatcher,首个用于大型语言模型的版权检测 API
在部署大型语言模型的公司中,管理由非预期的版权侵权所带来的风险应成为核心关注点……
《在自定义数据集上训练和运行 YOLOv8 模型的全面指南》
现在,通过 Python、命令行或 Google Colab 在自定义数据集上训练自己的计算机视觉模型比以往任何时候都更加容易。
·发表于 Towards Data Science ·阅读时长:15 分钟·2024 年 10 月 2 日
--
图片由作者使用 ChatGPT Auto 创建。
Ultralytics 的前沿YOLOv8模型是解决计算机视觉问题的最佳方法之一,同时最小化麻烦。它是 Ultralytics 的 YOLO (You Only Look Once) 系列模型的第八个也是最新的版本,像其他版本一样,使用卷积神经网络 (CNN) 来预测物体类别及其边界框。YOLO 系列物体检测器因其高准确度和快速性而广为人知,并提供了一个基于 PyTorch 的平台,简化了从头开始创建模型的过程。
重要的是,YOLOv8 也是一个非常灵活的模型。也就是说,它可以在各种平台上进行训练,使用你选择的任何数据集,且预测模型可以从多个来源运行。本指南将作为一个全面的教程,涵盖训练和运行 YOLOv8 模型的多种方式,以及每种方法的优缺点,这些内容将帮助你根据硬件和数据集选择最合适的流程。
注:创建此示例数据集所使用的所有图像均由作者拍摄。
环境
要开始训练我们的 YOLOv8 模型,第一步是决定我们希望在哪种环境中训练模型(请记住,训练和运行模型是两个独立的任务)。
我们可以选择的环境大致可以分为两类:本地环境和云环境。
在本地训练中,我们实际上是在直接使用设备的物理硬件运行训练过程。在本地训练中,YOLOv8 为我们提供了两种选择:Python API和CLI。这两种选择在结果和速度上没有真正的区别,因为它们背后运行的是相同的过程;唯一的区别在于训练的设置和执行方式。
另一方面,基于云的训练允许你利用云服务器的硬件。通过使用互联网,你可以连接到云运行时并执行代码,就像在本地机器上一样,只不过现在是在云硬件上运行。
到目前为止,最受欢迎的机器学习云平台是Google Colab。它使用 Jupyter 笔记本格式,允许用户创建“单元”,在其中编写和运行代码片段,并提供与 Google Drive 和 Github 的强大集成。
你决定使用哪个环境主要取决于你所拥有的硬件。如果你拥有一台配备高端 NVIDIA GPU 的强大系统,本地训练可能会非常适合你。如果你的本地机器硬件不符合机器学习的要求,或者你只是需要比本地硬件更多的计算能力,那么 Google Colab 可能是一个不错的选择。
Google Colab 的一个最大优势是它提供了一些免费的计算资源,并且还具有简单的升级路径,允许你利用更快的计算硬件。即使你已经有了一台强大的系统,如果 Google Colab 在其高阶计划中提供的更快 GPU 相比现有硬件能带来显著的性能提升,你也可以考虑使用 Google Colab。在免费计划中,你只能使用 NVIDIA T4,其性能大致相当于 RTX 2070。更高阶的计划中提供 L4(性能约等于 4090)和 A100(性能约等于两块 4090)。在比较 GPU 时,请记住VRAM的大小是机器学习性能的主要决定因素。
数据集
为了开始训练一个模型,你需要大量的数据来训练它。目标检测数据集通常由各种物体的图像集合组成,此外还包括指示物体在图像中位置的“边界框”。
被检测物体周围的边界框示例。图片来源:作者。
YOLOv8 兼容的数据集有特定的结构。它们主要分为valid、train和test文件夹,分别用于模型的验证、训练和测试(验证和测试的区别在于,验证过程中使用结果来调整模型以提高其准确性,而测试过程中,结果仅用于提供模型在现实世界中的准确性度量)。
在这些文件夹中,数据集进一步分为两个文件夹:images
和labels
文件夹。这两个文件夹的内容是紧密相关的。
images
文件夹顾名思义,包含数据集中的所有物体图像。这些图像通常具有正方形的长宽比、较低的分辨率和较小的文件大小。
labels
文件夹包含每个图像中边界框的位置和大小数据,以及每个图像表示的物体类型(或类别)。例如:
5 0.8762019230769231 0.09615384615384616 0.24519230769230768 0.18990384615384615
11 0.8846153846153846 0.2800480769230769 0.057692307692307696 0.019230769230769232
11 0.796875 0.2668269230769231 0.04807692307692308 0.02403846153846154
17 0.5649038461538461 0.29927884615384615 0.07211538461538461 0.026442307692307692
8 0.48197115384615385 0.39663461538461536 0.06490384615384616 0.019230769230769232
11 0.47716346153846156 0.7884615384615384 0.07932692307692307 0.10576923076923077
11 0.3425480769230769 0.5745192307692307 0.11057692307692307 0.038461538461538464
6 0.43509615384615385 0.5216346153846154 0.019230769230769232 0.004807692307692308
17 0.4855769230769231 0.5264423076923077 0.019230769230769232 0.004807692307692308
2 0.26322115384615385 0.3713942307692308 0.02403846153846154 0.007211538461538462
每一行代表图像中存在的一个独立物体。在每一行中,第一个数字表示物体的类别,第二和第三个数字表示边界框中心的 x 和 y 坐标,第四和第五个数字表示边界框的宽度和高度。
images
和labels
文件夹中的数据通过文件名相互关联。images
文件夹中的每张图像都有一个在labels
文件夹中对应的文件,文件名相同,扩展名不同。数据集中,images
和labels
文件夹中总是有成对的文件,它们的文件名相同,但扩展名不同; .jpg
用于图像,.txt
用于标签。每个.jpg
图片中物体的边界框数据包含在相应的.txt
文件中。
YOLOv8 兼容数据集的典型文件结构。来源:Ultralytics YOLO 文档(docs.ultralytics.com/datasets/detect/#ultralytics-yolo-format
)
有几种方法可以获取 YOLOv8 兼容的数据集来开始训练模型。你可以创建自己的数据集或使用互联网中的预配置数据集。为了本教程的目的,我们将使用CVAT来创建自己的数据集,并使用Kaggle来查找一个预配置的数据集。
CVAT
CVAT(cvat.ai)是一款标注工具,允许你通过手动为图像和视频添加标签来创建自己的数据集。
创建账户并登录后,开始标注的过程很简单。只需创建一个项目,为其取一个合适的名字,并添加你希望标注的物体类型/类别的标签。
在 cvat.ai 上创建新项目和标签,视频由作者提供。
创建一个新任务并上传你希望包含在数据集中的所有图像。点击“Submit & Open”,一个新的任务将在项目下创建,并附带一个工作。
在 cvat.ai 上创建新任务和工作,视频由作者提供。
打开此任务将允许你开始标注过程。使用矩形工具为数据集中的每张图像创建边界框和标签。
在 cvat.ai 上使用矩形工具创建边界框,视频由作者提供。
在标注完所有图像后,返回任务页面,选择“Actions” → “Export task dataset”,并选择YOLOv8 Detection 1.0作为导出格式。下载任务数据集后,你会发现它只包含labels文件夹,而没有images文件夹(除非在导出时选择了“保存图像”选项)。你需要手动创建images文件夹并将图像移动到其中(你可能想先将图像压缩到较低的分辨率,例如 640x640)。记住不要更改文件名,因为它们必须与labels文件夹中的.txt 文件的文件名匹配。你还需要决定如何将图像分配到valid
、train
和test
文件夹中(其中train
是最重要的)。
从 cvat.ai 导出的数据集示例,图片由作者提供。
你的数据集已经完成并准备好使用!
Kaggle
Kaggle (kaggle.com) 是最大的在线数据科学社区之一,也是探索数据集的最佳网站之一。你可以通过简单地搜索他们的网站来查找所需的数据集,除非你在寻找非常具体的内容,否则很有可能会找到。然而,Kaggle 上的许多数据集并不符合 YOLOv8 兼容格式和/或与计算机视觉无关,因此你可能需要在查询中加入“YOLOv8”来优化搜索结果。
你可以通过数据集的数据资源管理器(页面右侧)中的文件结构判断一个数据集是否为 YOLOv8 兼容格式。
一个 YOLOv8 兼容格式的数据集示例,图片由作者提供。
如果数据集相对较小(几 MB)和/或你在本地训练,你可以直接从 Kaggle 下载数据集。然而,如果你计划在 Google Colab 上训练一个大型数据集,最好从 notebook 本身获取数据集(更多信息见下文)。
训练
训练过程将根据你是本地训练还是在云端训练而有所不同。
本地
创建一个用于存放所有训练文件的项目文件夹。本教程中我们将其命名为yolov8-project
。将数据集移动/复制到此文件夹。
设置一个包含 YOLOv8 所需依赖项的 Python 虚拟环境:
python3 -m venv venv
source venv/bin/activate
pip3 install ultralytics
创建一个名为config.yaml
的文件。在这里,重要的训练数据集信息将被指定:
path: /Users/oliverma/yolov8-project/dataset/ # absolute path to dataset
test: test/images # relative path to test images
train: train/images # relative path to training images
val: val/images # relative path to validation images
# classes
names:
0: bottle
在path
中,填写数据集根目录的绝对文件路径。你也可以使用相对文件路径,但这取决于config.yaml
的相对位置。
在test
、train
和val
中,填写用于测试、训练和验证的图像位置(如果你只有train
图像,可以将所有三个位置都设置为train/images
)。
在names
下,指定每个类别的名称。这些信息通常可以在任何 YOLOv8 数据集的data.yaml
文件中找到。
如前所述,可以使用Python API或CLI进行本地训练。
Python API
创建另一个名为main.py
的文件。这将是实际训练开始的地方:
from ultralytics import YOLO
model = YOLO("yolov8n.yaml")
model.train(data="config.yaml", epochs=100)
通过将模型初始化为YOLO("yolov8n.yaml")
,我们实际上是从头创建一个新模型。我们使用yolov8n
是因为它是最快的模型,但你也可以根据自己的使用情况选择其他模型。
YOLOv8 变体的性能指标。来源:Ultralytics YOLO 文档(docs.ultralytics.com/models/yolov8/#performance-metrics
)
最后,我们训练模型并传入配置文件和epochs(训练轮次)。一个好的基准是 300 个 epochs,但你可能需要根据数据集的大小和硬件的速度调整这个数字。
还有一些有用的设置你可能想要包含:
-
imgsz
:将所有图像调整为指定的大小。例如,imgsz=640
将所有图像调整为 640x640。这在你创建了自己的数据集并且没有调整图像大小的情况下非常有用。 -
device
:指定要训练的设备。默认情况下,YOLOv8 尝试在 GPU 上进行训练,并使用 CPU 训练作为后备,但如果你在 M 系列 Mac 上进行训练,你必须使用device="mps"
,以便通过 Apple 的Metal 性能着色器(MPS)后端进行 GPU 加速。
有关所有训练参数的更多信息,请访问 docs.ultralytics.com/modes/train/#train-settings
。
你的项目目录现在应该类似于以下结构:
项目目录的示例文件结构。图片由作者提供。
我们终于准备好开始训练我们的模型了。在项目目录中打开一个终端并运行:
python3 main.py
终端将显示每个 epoch 的训练进度信息。
每个 epoch 的训练进度将在终端显示。图片由作者提供。
训练结果将保存在 runs/detect/train
(或 train2
、train3
等)中。包括 权重(扩展名为 .pt
的文件),这些将对稍后运行模型非常重要,以及 results.png
,其中显示了包含相关训练统计信息的多张图表。
来自 results.png 的示例图表。图像由作者提供。
CLI
在项目目录中打开一个新的终端并运行以下命令:
yolo detect train data=config.yaml model=yolov8n.yaml epochs=100
此命令可以根据上面列出的 Python API 中的相同参数进行修改。例如:
yolo detect train data=config.yaml model=yolov8n.yaml epochs=300 imgsz=640 device=mps
训练将开始,进度将在终端显示。其余的训练过程与 Python CLI 中相同。
Google Colab
访问 colab.research.google.com/
并创建一个新的训练笔记本。
在训练之前,确保通过在右上角选择 更改运行时类型 来连接到 GPU 运行时。在 CPU 运行时上,训练将非常缓慢。
将笔记本运行时从 CPU 更改为 T4 GPU。视频由作者提供。
在我们可以开始在 Google Colab 上训练之前,我们首先需要将数据集导入到笔记本中。直观上,最简单的方法是将数据集上传到 Google Drive,然后从那里导入到我们的笔记本中。然而,上传任何大于几 MB 的数据集都需要极长的时间。解决办法是将数据集上传到一个远程文件托管服务(如 Amazon S3 或甚至 Kaggle),然后直接从那里将数据集拉入我们的 Colab 笔记本。
从 Kaggle 导入
以下是如何直接将 Kaggle 数据集导入 Colab 笔记本的说明:
在 Kaggle 账户设置中,向下滚动至 API 并选择 创建新令牌。这将下载一个名为 kaggle.json
的文件。
在笔记本单元格中运行以下命令:
!pip install kaggle
from google.colab import files
files.upload()
上传刚刚下载的 kaggle.json
文件,然后运行以下命令:
!mkdir ~/.kaggle
!cp kaggle.json ~/.kaggle/
!chmod 600 ~/.kaggle/kaggle.json
!kaggle datasets download -d [DATASET] # replace [DATASET] with the desired dataset ref
数据集将作为 zip 压缩包下载。使用 unzip
命令解压内容:
!unzip dataset.zip -d dataset
开始训练
在笔记本的文件浏览器中创建一个新的 config.yaml
文件,并按照之前的描述进行配置。Colab 笔记本中的默认工作目录是 /content/
,因此数据集的绝对路径将是 /content/[dataset folder]
。例如:
path: /content/dataset/ # absolute path to dataset
test: test/images # relative path to test images
train: train/images # relative path to training images
val: val/images # relative path to validation images
# classes
names:
0: bottle
确保检查数据集的文件结构,确保 config.yaml
中指定的路径是准确的。有时数据集会嵌套在多个文件夹层级中。
将以下命令作为单元格运行:
!pip install ultralytics
import os
from ultralytics import YOLOmodel = YOLO("yolov8n.yaml")
results = model.train(data="config.yaml", epochs=100)
前面提到的用于修改本地训练设置的参数同样适用于此处。
与本地训练类似,结果、权重和图表将保存在 runs/detect/train
中。
运行中
无论是在本地还是在云端进行训练,预测必须在本地运行。
模型训练完成后,runs/detect/train/weights
文件夹中会有两个权重文件,分别名为 best.pt
和 last.pt
,它们是最佳周期和最新周期的权重文件。对于本教程,我们将使用 best.pt
来运行模型。
如果你在本地训练,移动 best.pt
到一个方便的位置(例如我们的项目文件夹 yolov8-project
)以便运行预测。如果你在云端训练,将 best.pt
下载到你的设备上。在 Google Colab 上,右击文件浏览器中的文件并选择 下载。
在 Google Colab 上下载权重。视频由作者提供。
与本地训练类似,预测可以通过Python API 或 CLI 运行。
Python API
在 best.pt
所在的同一位置,创建一个名为 predict.py
的新文件:
from ultralytics import YOLO
model = YOLO("best.pt")
results = model(source=0, show=True, conf=0.25, save=True)
与训练类似,有许多有用的参数可以修改预测设置:
-
source
: 控制预测的输入源。source=0
设置摄像头为输入源。更多信息见下文。 -
show
: 如果为True
,则在屏幕上显示预测、边界框和置信度。 -
conf
: 用于判断预测是否被接受的最小置信度阈值。 -
save
: 如果为True
,将预测结果保存到runs/detect/predict
(或predict2
、predict3
等)文件夹中。 -
device
: 如前所述,在 M 系列 Mac 上使用device="mps"
。
要查看完整的预测参数列表,请访问 docs.ultralytics.com/modes/predict/#inference-arguments
。
CLI
运行以下命令以启动模型:
python3 predict.py
通过实时摄像头视频流运行 YOLOv8 模型预测。视频由作者提供。
CLI
yolo detect predict model=best.pt source=0 show=True conf=0.25 save=True
这些参数与 Python API 中的参数相同。
实现
我们现在已经成功地在实时摄像头视频流上运行了我们的模型,但这又意味着什么呢?我们如何实际使用这个模型并将其集成到项目中?
让我们从输入和输出的角度来思考它。为了使这个模型在外部应用中对我们有用,它必须能够接受有用的输入并产生有用的输出。幸运的是,YOLOv8 模型的灵活性使得它可以被集成到多种应用场景中。
我们使用 source=0
将摄像头设置为我们的预测输入源。然而,YOLOv8 模型可以使用比这更多的输入源。以下是几个示例:
results = model(source="path/to/image.jpg", show=True, conf=0.25, save=True) # static image
results = model(source="screen", show=True, conf=0.25, save=True) # screenshot of current screen
results = model(source="https://ultralytics.com/images/bus.jpg", show=True, conf=0.25, save=True) # image or video URL
results = model(source="path/to/file.csv", show=True, conf=0.25, save=True) # CSV file
results = model(source="path/to/video.mp4", show=True, conf=0.25, save=True) # video file
results = model(source="path/to/dir", show=True, conf=0.25, save=True) # all images and videos within directory
results = model(source="path/to/dir/**/*.jpg", show=True, conf=0.25, save=True) # glob expression
results = model(source="https://www.youtube.com/watch?v=dQw4w9WgXcQ", show=True, conf=0.25, save=True) # YouTube video URL
要查看完整的预测源和输入选项列表,请访问 docs.ultralytics.com/modes/predict/#inference-sources
。
每当我们运行预测时,YOLOv8 会返回大量有价值的数据,这些数据以 Results
对象列表的形式呈现,包含关于边界框、分割掩码、关键点、类别概率和定向边界框(OBB)的信息。
由于我们在代码中将预测结果分配给了results
变量,我们可以使用它来获取有关预测的信息:
from ultralytics import YOLO
model = YOLO("best.pt")
results = model(source="bottles.jpg", show=True, conf=0.25, save=True)
print("Bounding boxes of all detected objects in xyxy format:")
for r in results:
print(r.boxes.xyxy)
print("Confidence values of all detected objects:")
for r in results:
print(r.boxes.conf)
print("Class values of all detected objects:")
for r in results:
print(r.boxes.cls)
由于输出结果的类型繁多,无法在本教程中全部涵盖,但你可以通过访问docs.ultralytics.com/modes/predict/#working-with-results
了解更多内容。
这只是你可以使用 YOLOv8 模型输出的一个非常基础的示例,实际上有无数种方法可以将模型应用到你自己的项目中。
结论
恭喜你坚持到最后!
在本文中,我们能够从零开始,制作自己的 YOLOv8 兼容数据集,从 Kaggle 导入数据集,使用包括 Python API、CLI 和 Google Colab 在内的多个环境训练模型,运行本地模型,并探索许多输入/输出方法,使我们能够在自己的项目中利用 YOLOv8 模型。
请记住,本教程的目的是作为 YOLOv8 或计算机视觉的入门点或介绍。我们只是略微触及了 YOLOv8 模型的复杂性,随着你对 YOLOv8 和计算机视觉的进一步了解,深入探索这一主题绝对是明智之举。互联网上有大量的文章,Medium 上也有很多内容,专门为此目的而写。
话虽如此,如果你跟随本教程并完成了最后的部分,这仍然是一个伟大的成就。我希望这篇文章能帮助你对机器学习、计算机视觉以及 YOLOv8 模型有一个基本的理解。也许你已经对这个主题产生了兴趣,并将在未来继续学习更深入的内容,挑战更高阶的课题。
感谢阅读,祝你度过愉快的一天!
人工智能生成内容的文化影响:第一部分
当人工智能生成的媒体在我们的生活中变得无处不在时,会发生什么?这与我们之前经历的有什么关系,又会如何改变我们?
·发表于Towards Data Science ·7 分钟阅读·2024 年 12 月 3 日
--
图片来源:Annie Spratt 来自Unsplash
这是我撰写的两部分系列文章的第一部分,分析人工智能生成内容如何影响人们和社区。我已经详细讨论过与此相关的环境、经济和劳动问题,以及歧视和社会偏见。但这一次,我想更深入地探讨一下人工智能生成的媒体和内容对我们心理和社会的影响,特别是它们对我们批判性思维、学习和概念化知识的关系。
历史
伪造行为从摄影发明之初就开始出现。当我们开始拥有一种被认为能展示现象和事件真实、不加修饰的媒体形式时,就是人们开始想出各种方法来操控这种媒体形式的时刻,这些方法在艺术和哲学上产生了巨大的影响。(同时也有幽默或纯粹的欺诈效果。)尽管如此,我们对照片仍然存在一种不合理的信任,并且我们与这种形式的关系在信任与怀疑之间达到了某种平衡。
当我还是个孩子时,互联网还没有广泛普及,绝大多数家庭没有接入,但等到我成为青少年时,这一切发生了彻底的变化,我认识的每个人都在使用 AOL 即时消息。大约在我离开研究生院时,iPhone 发布了,智能手机时代开始了。我重述这些,是为了强调文化创造和消费在短短几十年间发生了惊人且难以辨认的变化。
我认为现在的时刻代表了一个全新时代,特别是在我们消费和创造的媒体和文化内容方面,因为生成式 AI 的推出。这有点像当 Photoshop 开始广泛普及时,我们开始意识到照片有时被修饰过,且我们开始质疑是否能相信图片的真实性。(读者可能会觉得关于“什么是照片”的持续讨论是这个问题的有趣延伸。)但即便如此,Photoshop 价格昂贵,而且使用它需要一定的技能,所以我们接触到的大多数照片都相对真实,而我认为人们普遍期待广告和电影中的图像不会是“真实”的。我们的预期和直觉必须适应技术的变化,而我们或多或少做到了这一点。
当前
如今,AI 内容生成器已经使得人工生成或修改任何类型的内容,包括图像,变得触手可及。不幸的是,要估算在线上有多少内容可能是由 AI 生成的,这个问题极其困难——如果你搜索这个问题,你会看到有参考资料,链接到一篇Europol 文章,宣称到 2026 年这一比例会达到 90%——但读了之后你会发现,这篇研究论文根本没有这么说。你还可能会发现一篇由一些 AWS 研究人员撰写的论文,引用了 另一个出处,说 57% 可能是这个比例——但那也是一种错误的理解(他们谈论的是机器翻译的文本内容,而不是完全由机器生成的文本,更别提图像或视频了)。就我所知,目前没有可靠的、基于科学的研究来确切表明我们所消费的内容中到底有多少是 AI 生成的——即使有,发布的那一刻起,它也会过时。
但如果你仔细想想,这其实是非常合理的。AI 生成内容不断涌现的一个主要原因是,因为现在比历史上任何时候都更难判断你所看到的是否真的是由人类创造的,以及这种表现是否反映了现实。当你根本无法明确辨认出它是什么时,你怎么去统计它,或者即使估算也变得如此困难呢?
我认为我们都曾有过看到来源可疑的内容的经历。我们看到的图像似乎处于“恐怖谷”中,或者强烈怀疑某个零售网站上的产品评论听起来过于正面和通用,觉得那肯定是通过生成式 AI 和机器人创建的。女士们,最近你们有没有尝试在网上寻找理发灵感图片?根据我个人的经验,Pinterest 或其他类似网站上超过 50% 的图片明显是 AI 生成的,具有明显的特征:没有纹理的皮肤、橡胶般的面部特征、带子和项链消失在无处、图像明显不包括手部、从正面永远看不到两只耳朵等等。这些图片很容易被忽略,但大量这样的图片会让你开始怀疑自己看到的是经过重度滤镜处理的真实图像,还是完全由 AI 生成的内容。我一向把了解这些东西当成自己的事,而我自己有时也不确定。听说在约会应用上,单身男性被基于生成式 AI 的诈骗机器人搞得喘不过气来,甚至有一个方法可以检查——“土豆测试”。如果你让机器人说“土豆”,它会忽略你,但一个真实的人可能会做出来。我们生活中的小细节正在被 AI 内容渗透,而这一切并没有得到我们的同意或批准。
为什么?
把这些 AI 垃圾内容扔进这么多在线空间到底有什么意义?最理想的情况是希望人们点击进入那些有广告的网站,提供一些足够让人信服的无意义文字和图片,来获得那些宝贵的广告展示量,从而从广告商那里赚取几分钱。人工生成的产品评论和图片数量庞大,以至于代发货商和便宜货供应商可以通过它们欺骗消费者购买一些比竞争对手便宜一点的东西,让他们以为自己买到了正品。也许这个商品便宜得离谱,失望的买家就会默默接受损失,而不去麻烦退钱。
更糟糕的是,使用大型语言模型(LLM)生成文本和图像的机器人可以用来诱骗人们上当,而因为唯一真正需要的资源是计算能力,这种骗局的规模化成本几乎为零——如果你能时不时地骗到一个人的钱,这笔开销是完全值得的。AI 生成的内容被用于犯罪滥用,包括猪肉切割骗局、AI 生成的 儿童性虐待材料 和非自愿的亲密图像,这些也可能变成敲诈勒索的手段。
也有AI 生成图像的政治动机,视频和文本——在美国选举年,世界各地的不同实体出于不同的角度和目标,制作了 AI 生成的图像和视频以支持他们的观点,并通过生成式 AI 机器人向社交媒体(尤其是前 Twitter)传播宣传信息,内容审查工作已基本停止,防止滥用、骚扰和偏执。传播这些内容的预期是,信息不充分的互联网用户将通过不断重复接触这些内容来吸收其信息,而每当他们意识到某个项目是人工合成时,仍会有无数人接受它为真实。此外,这些材料创造了一个信息生态系统,在这个生态系统中,真相无法定义或证明,从而削弱了良好行为者及其尝试突破噪音的努力。
网络上少数的 AI 生成内容将是实际尝试创建吸引人的图像以供娱乐,或相对无害的套话文本生成,用于填充企业网站,但正如我们大家都很清楚的那样,互联网充斥着诈骗和一夜暴富的骗子,而生成式 AI 的进步已将我们带入了这些行业的全新时代。(而且,这些应用对真正的创作者、能源和环境以及其他问题有着巨大的负面影响。)
我们所处的位置
我意识到我正在描绘我们在线生态系统的一个相当严峻的画面。不幸的是,我认为这很准确,而且只会变得更糟。我并不是说生成式 AI 没有好的用途,但我越来越确信,对我们社会的负面影响将会比正面影响更大、更直接、更有害。
我是这样思考的:我们已经达到了一个时刻,是否可以信任我们所看到或阅读的内容变得不再明确,而且我们经常无法知道我们在线遇到的实体是人类还是 AI。这会如何影响我们对所遇到内容的反应?如果期望我们的思维方式不会因此发生变化,那是愚蠢的,我非常担心我们正在经历的变化并非朝着更好的方向发展。
然而,模糊性是一个很大的挑战。问题不在于我们知道自己在消费不可信的信息,而是它本质上是不可知的。我们永远无法确定。批判性思维和批判性媒体消费习惯有所帮助,但生成式 AI 内容的扩展可能正在超越我们的批判性能力,至少在某些情况下是如此。在我看来,这对我们对信息的信任和信心的概念有着真正的影响。
在我的下一篇文章中,我将详细讨论这可能对我们对周围世界的思维和观念产生何种影响,并考虑如果有的话,我们的社区可能会对此做些什么。
阅读更多我的作品,请访问 www.stephaniekirmer.com.
此外,常规读者会知道我按两周一次的时间表发布文章,但我将改为按月发布。感谢您的阅读,期待继续分享我的想法!
进一步阅读
www.theverge.com/2024/2/2/24059955/samsung-no-such-thing-as-real-photo-ai
arxiv.org/pdf/2401.05749
— Brian Thompson, Mehak Preet Dhaliwal, Peter Frisch, Tobias Domhan, 和 AWS 的 Marcello Federico
www.404media.co/ai-generated-child-sexual-abuse-material-is-not-a-victimless-crime/
www.404media.co/fbi-arrests-man-for-generating-ai-child-sexual-abuse-imagery/
一些 AI 工具的最有害应用并不是隐藏在互联网的黑暗角落,而是积极存在…
www.smithsonianmag.com/innovation/history-spirit-photography-future-deepfake-videos-180979010
www.brennancenter.org/our-work/research-reports/generative-ai-political-advertising
康威定律与数据空间
现代趋势如何追溯到康威定律
·发表于 Towards Data Science ·12 分钟阅读·2024 年 10 月 25 日
--
图片由作者提供。(由 Midjourney 生成,并用 Krita 进行修饰)
本文最初发表于 我的博客 上 https://jack-vanlightly.com。
本文的灵感来源并延伸了 Bernd Wessely 在《数据架构:经验教训》一文中的“警惕孤岛化专业化”部分 Data Architecture: Lessons Learned**。它汇集了我观察到的几个趋势,以及我在软件与数据团队分割两边工作二十年的经验所形成的个人看法。
康威定律:
“任何设计系统的组织(广义定义)都会产生一个结构,其结构是该组织沟通结构的复制。” — Melvin Conway
这一现象在全球范围内的数十万家组织中都有上演,尤其在软件开发与数据分析团队之间的分裂中体现得尤为明显。这两组通常有不同的汇报结构,直到或紧接着高层管理团队。
这个问题如今已经存在,并且只会不断加剧。
Jay Kreps 五年前提到组织正在变成软件:
“问题不仅仅在于企业使用更多的软件,而是越来越多的企业在软件中得以定义。也就是说,一个企业执行的核心流程——从它如何生产产品,到它如何与客户互动,再到它如何提供服务——越来越多地在软件中被明确、监控和执行。” — Jay Kreps
这一软件的有效性与组织的成功直接相关。如果软件存在功能障碍,组织也会出现问题。反过来,组织结构上的问题也会在软件中体现出来。这意味着,一个想要在其领域中脱颖而出的公司,可能会在执行上落后于竞争对手,反应市场条件的速度也太慢。这样的情况已经被说了无数次,但这依然是一个基本的真理。
当“软件工程”团队和“数据”团队在各自的汇报结构中各自为政时,就会产生一种悲剧性的喜剧局面,最终最大输家是整个企业。
变革的风正在吹起
图片由作者提供。(由 Midjourney 生成,使用 Krita 进行了修饰)
越来越多的迹象表明,当前“我们与他们”的态度正发生变化,软件和数据团队之间的目标对立或完全忽视彼此的需求、激励和对企业成功的贡献的状况正在被改变。在过去两年中,数据分析领域出现了三大关键趋势,这些趋势有潜力带来真正的改进。每一个趋势仍然处于初步阶段,但正在获得动力:
-
数据工程是软件工程的一个学科。
-
数据契约和数据产品。
-
向左移动(Shift Left)。
阅读完本文后,我相信你会同意这三者紧密相连。
数据工程是软件工程的一个学科。
数据工程已经发展成为与软件工程分开的学科,原因有很多:
-
数据分析/商业智能(BI)领域,其中实践数据工程的地方,历来是与软件开发分开的业务职能。这导致了文化上的分歧,双方没有互相倾听或学习。
-
数据工程解决的问题与传统的软件开发有所不同,因此使用的工具也不同。
-
数据工程在过去 25 年中发生了巨大变化。许多新问题出现,需要从根本上重新思考技术,这导致了一段漫长的、混乱的实验和创新期。
尽管技术仍在发展,但尘埃大致已经落定。我们有时间整合并审视我们所处的状态。数据社区开始意识到,许多当前的问题实际上与软件开发领域的问题并没有本质区别。数据团队像软件工程师一样编写软件并与软件系统互动。
软件的类型可能不同,但许多来自软件工程的实践同样适用于数据和分析工程:
-
测试。
-
良好的稳定 API。
-
可观察性/监控。
-
模块化与重用。
-
在开发过程中后期修复错误的成本比在早期解决它们的成本更高。
是时候让数据和分析工程师认同自己是软件工程师,并定期将更广泛的软件工程学科的实践应用到自己的子学科中。
数据合同与数据产品
数据合同在 2022/2023 年间因应对数据管道不断崩溃修复和数据团队表现不佳的挫败感而迅速兴起。它迅速传播开来,大家纷纷讨论数据合同,尽管具体如何实现数据合同的细节并不多见。但目标是明确的:解决数据管道崩溃的问题。
由于多种原因,数据管道崩溃:
-
软件工程师并不知道数据工程师在他们的应用数据库之上构建了什么,因此没有对表格模式的变更提供任何保证,甚至没有警告即将发生的可能破坏数据管道的变化(通常是因为他们根本不知道)。
-
数据工程师由于组织功能失调或孤立,通常无法与他们依赖的软件团队建立健康的同行关系。或者即使能够建立关系,软件团队领导也没有支持数据团队获取所需数据的意愿,除了提供数据库凭证。结果就是直接从数据源中获取数据,破坏了长期以来软件工程中封装的实践(并因此遭受后果)。
最近我听了Super Data Science E825这一期节目,嘉宾是数据合同的倡导者 Chad Sanderson。我非常喜欢他对这一术语的定义:
我对数据质量的定义与其他人的有些不同。在软件领域,人们通常将质量视为非常确定的东西。比如,我在写一个功能,构建一个应用程序,我有一套该应用的需求,如果软件不再满足这些需求,那就叫做一个 bug,属于质量问题。但在数据领域,可能有一个数据生产者在以某种方式生成或收集数据,这种改变对他们的用例是完全合理的。举个例子,也许我有一个名为 timestamp 的列,它以本地时间记录,但我决定将其更改为 UTC 格式。这完全没有问题,也完全合理,可能是你应该做的事情。但是,如果我下游的某个使用者预期的是本地时间,那么他们就会遇到数据质量问题。因此,我的观点是,数据质量实际上是数据生产者与数据消费者之间管理不当的预期的结果,而数据合同的作用正是帮助这两方更好地协作。——Chad Sanderson
数据合同的构成仍然在一定程度上开放解释和实现,涉及具体的技术和模式。架构管理是一个核心主题,但仅是解决方案的一部分。数据合同不仅仅是指定数据的形状(其架构);它还涉及信任和可靠性,我们可以从 REST API 社区中理解这一点:
-
REST API 通常通过 OpenAPI 来进行文档编制,这是一个 REST API 规范工具。它本质上是请求和响应的架构,以及安全方案。
-
REST API 是有版本的,并且非常小心地对其进行版本管理,以避免引入破坏性变化。当发生破坏性变化时,API 会发布一个新的主版本。API 版本控制是一个深入的话题,关于最佳选项的讨论历史悠久。但重点是,软件工程社区已经经过深思熟虑,思考如何演化 API。
-
一个不断变化并因破坏性变化而发布新主版本的 REST API 是一个糟糕的 API。发布 API 给客户的组织必须确保他们不仅要创建一个设计良好并且指定清晰的 API,还要确保其稳定,不会频繁变化。
在软件工程中,当 Service A 需要 Service B 的数据时,Service A 完全不会直接访问 Service B 的私有数据库。发生的情况是:
-
两个服务的工程领导/团队建立了沟通渠道,最初可能是面对面的对话。
-
Service A 团队为 Service B 安排了一个精心设计的接口,确保不会破坏 Service A 的封装。这可能会导致一个 REST API,或者是一个事件流或队列,供 Service B 消费。
-
Service A 团队承诺将继续维护这个 API/流/队列。这涉及到随着时间推移不断演化它,为 Service B 提供一个稳定且可预测的接口。部分维护工作可能会由一个平台团队承担,平台团队的责任是为开发团队提供基础设施构件。
为什么 Service A 团队要为 Service B 团队做这些工作?是出于无私吗?不是。他们之所以合作,是因为这样做对业务有价值。一个管理得当的组织秉持着 #OneTeam 的座右铭,组织会做出必要的事情,以高效和有效的方式运作。这意味着,Service A 团队有时必须为其他团队的利益做工作。这是因为上层管理层的激励目标对齐。
软件工程中也有一个众所周知的事实,那就是在开发周期的后期,或者更糟糕的是在生产环境中修复漏洞,远比在早期解决这些问题要昂贵得多。回到一周或一个月前的工作去修改问题,严重干扰了软件过程,而生产环境中的漏洞可能引发各种不良后果。提前做些工作,生产出设计良好、稳定的 API 会让每个人的生活更轻松。对此有一句话:“一盎司的预防胜过一磅的治疗。”
这些 API 是契约。它们通过在软件团队之间建立沟通来达成,并在明确 ROI(投资回报率)足够时进行实现。其实就这么简单。由于软件领导层的激励一致,通常在软件工程部门内部是这样运作的。
数据产品
API(或应用程序编程接口)这个术语并不完全适用于“数据”。因为产品本身是数据,而不是某些业务逻辑之上的接口,所以“数据产品”这个术语更为恰当。单词“产品”也暗示着某种质量附带,某种程度的专业性和可靠性。这就是为什么数据契约与数据产品密切相关,数据产品是更抽象的数据契约的具象化体现。
数据产品与软件端的 REST API 非常相似。它归结为团队之间开启沟通渠道、严格规范数据形状(包括从查德的言论中提到的时区)、随着不可避免的变化小心演进,以及数据生产者承诺为消费者维护稳定的数据 API。不同之处在于,数据产品通常是表格或流(数据本身),而不是 HTTP REST API,后者通常驱动某些逻辑或每次调用时检索一个实体。
另一个关键的见解是,正如 API 使得服务以可预测的方式可重用,数据产品则使得数据处理工作更具可重用性。在软件领域,一旦“订单 API”发布,所有需要与订单子系统交互的下游服务都会通过该 API 进行操作。并不会为每个下游使用场景建立一堆一次性接口。然而,这正是我们在数据工程中常见的现象,即单次使用的管道和为不同使用场景复制的源数据。
简单来说,软件工程通过模块化(无论是实际的软件模块还是 API)促进了软件的可重用性。数据产品在数据方面也做到了这一点。
向左移
Shift Left 概念源于网络安全领域。安全 historically 也是另一个孤岛,软件团队和安全团队各自有不同的报告结构、工具、激励措施,并且共享的词汇极少。结果是,我们已习惯了日益严重的安全危机,以至于下一个百万级数据泄露事件几乎没有被报道。我们已对它麻木,以至于可能不会认为这是个危机,但当你看到勒索软件团伙、信息窃取者和勒索者留下的破坏痕迹时,很难说这应该当作日常业务来处理。
Shift Left 的理念是将安全关注点向左移,即将安全措施嵌入到软件开发的过程中,而不是由一个与正在开发、修改和部署的软件几乎没有关联的独立团队在事后应用。它不仅仅是将安全集成到开发过程中,更是关于提高网络遥测数据的质量。网络遥测的异质性和普遍的“混乱”促使了这一将处理、清理和上下文化工作移到数据产生源头的运动。一旦失去来源信息,推理这些数据就变得异常具有挑战性。虽然网络数据异常具有挑战性,但在这一领域的经验教训是可以推广到其他领域的,例如数据分析。
网络安全孤岛与数据分析孤岛的相似性令人震惊。孤岛假设孤岛功能可以作为一个独立的单元运作,与其他业务功能分离。然而,网络安全和数据分析都是跨职能的,它们必须与企业中的许多不同领域互动。跨职能团队不能在幕后、事后或者旁边运作。孤岛行不通,而 Shift Left 则是要推翻这些孤岛,并用一些更加嵌入在软件开发过程中、不那么集中化的方式替代它们。
Bernd Wessely 在 TowardsDataScience 上写了一篇关于“信息孤岛”问题的精彩文章。他在文中指出,数据分析孤岛问题已经根深蒂固,以至于当前的实践几乎没有受到质疑。他认为,由“先接收再处理”组成的数据孤岛,“只是一个不合适的数据管理方式的临时解决方案。这个临时解决方案是因为当前企业在处理数据时的方式完全不适当所必需的。”
可悲的是,这一切并不新鲜。我从职业生涯开始就一直在阅读关于打破信息孤岛的文章,然而我们依然在 2024 年讨论打破孤岛的必要性!但我们必须打破它们!
如果数据孤岛是那个与组织其他软件分离的集中式庞然大物,那么 Shift Left 就是要将数据基础设施集成到软件开发、运营所在的环境中。
服务 B 不仅仅是直接访问服务 A 的私有内部;相反,创建了一个接口,允许服务 A 在不违反封装的情况下从服务 B 获取数据。这个接口——无论是 API、队列还是流——成为了一种稳定的数据消费方式,不会因为每次服务 A 需要更改其隐藏的内部结构时而中断。提供该接口的责任落在了服务 A 的团队上,因为这是正确的解决方案,但这背后也有商业理由。同样的原则适用于 Shift Left;与其将使数据可用的责任放在需要使用数据的人身上,不如将这个责任上游,交给数据产生和维护的地方。
这个向左转变的核心是数据产品。无论是事件流还是 Iceberg 表格,数据产品通常最好由拥有底层数据的团队来管理。这样,我们就避免了那些临时的、仓促的解决方案,这些解决方案绕过了良好的实践。
为了实现这一目标,我们需要以下条件:
-
各方之间的沟通与协调。这需要一定的业务成熟度才能实现,但在我们做到之前,我们会在未来十年或二十年内继续谈论打破孤岛,或者直到人工智能取代我们为止。
-
使生产、维护和支持数据产品更加容易的技术解决方案。
我们看到这个领域正在发生许多变化,从目录、治理工具、表格格式(如 Apache Iceberg)到丰富的事件流选项。这里有大量的开源项目,也有大量的供应商。构建数据产品的技术和实践仍处于早期阶段,但预计这个领域将迅速发展。
结论
你可能会认为大多数数据平台工程是在解决大规模的技术问题。不幸的是,再次是人与人之间的问题占据了主导地位。— Birdy
组织正在变成软件,而软件则根据业务的沟通结构进行组织;因此,如果我们想要解决软件/数据/安全孤岛问题,那么解决方案就在沟通结构中。
让数据分析在企业中更具影响力的最有效方法是解决康威定律问题。它导致了数据团队与更广泛的软件工程学科的文化和技术分离,以及沟通结构薄弱和缺乏共同理解。
结果是:
-
双方之间的合作与协调不良,导致了:
– 在操作层面(软件服务)和数据分析层面之间的混乱集成。
– 针对操作层面所做的更改,数据分析层面不断进行修复工作。
-
软件工程师用来使软件开发更具成本效益和可靠性的众多优秀实践往往被忽视。
实现更为一体化的软件和数据分析世界的障碍在于数据团队的持续孤立,以及激励机制的不对齐,这阻碍了软件团队和数据团队之间的合作。我相信,拥抱#OneTeam 的组织,能够让这两个团队进行对话、合作,甚至在某些程度上融合,将会看到最大的投资回报。一些组织可能已经这样做了,但这还远未普及。
事物在变化,态度也在变化。数据工程就是软件工程,数据契约/产品,以及向左转的兴起,都是重要的领先指标。
环绕在我们周围的数据:从体育到家庭管理
·发表于 走向数据科学 ·通过 通讯 发送 ·4 分钟阅读·2024 年 9 月 12 日
--
想写你的第一篇 TDS 文章吗?我们始终欢迎新作者的投稿。
时不时地,回顾并欣赏强大数据分析能够为日常生活的各个方面带来的价值是值得的。我们通常专注于商业成果和产品开发,这是有充分理由的,但这个世界远远超出了数据科学家常见的工作流程,探索这一领域的丰富性对于实践者来说,能够帮助他们提升,无论他们目前从事的是哪类项目。
为了庆祝数据驱动方法的多样化应用,并鼓励我们的读者拓宽技能和想象力,我们非常高兴地分享一系列精彩的文章,这些文章带领我们踏上了与数据的意外旅程——从运动竞赛中的得分模式到婚礼上的优化桌位安排。祝您阅读愉快!
-
家庭数据科学:使用蒙特卡洛和遗传算法解决保姆排班难题 “想到工作会议、午休时间和不可预测的班次,脑海里总是不断打转——直到我意识到,我可以使用解决商业问题的相同算法来解决一个非常个人的问题。” Courtney Perigo 带我们走过了她为解决保姆排班这一棘手问题而创造的细致问题解决过程,旨在确保在最需要的时刻安排好儿童照看。
-
多项体育项目中的不均衡计分 由于巴黎奥运会的记忆尚新,现在正是深入探讨数据科学与体育交叉领域的好时机。David Mulholland 选择了一个看似小众但实际上具有深远影响的话题——十项全能和七项全能中的复杂计分系统,并且通过细致的分析展示了如何通过智能数据分析揭示那些难以察觉的模式和见解。
摄影作品由Kenny Eliason提供,来源于Unsplash。
-
黄金的价格:奥林匹克成功是富人专属吗? 继续探讨体育与数据的主题,Maria Mouschoutzi 博士 借助她在统计学和数据可视化方面的知识,以及作为一名艺术体操运动员的经验,试图回答一个复杂的问题:一个国家的经济状况在多大程度上影响其奥运奖牌数量?
-
爱的数学:使用 Python 优化婚礼宴会厅座位安排 聚焦于受限的二次多背包问题(RQMKP)、数学规划和 Python, Luis Fernando PÉREZ ARMAS 博士 利用婚礼座位安排这一复杂的艺术展示了数据和数学如何帮助我们解决现实世界的问题——并概述了几种可以应用于其他更日常情境的扩展和高级方法。
如果你准备好重新投入到我们的核心数据科学和机器学习话题,我们本周有一系列一流的文章推荐给你:
-
为了简化并定制她的研究和演示流程,Lingzhen Chen转向了最近推出的 LlamaIndex 功能,并解释了如何有效使用它。
-
在地理空间数据、机器学习和环境研究的交汇点上,Conor O'Sullivan权衡了深度学习方法的优缺点在海岸侵蚀监测中的应用。
-
刚开始接触强化学习吗?不要错过Jesse Xia的适合初学者的指南,使用的是 OpenAI Gymnasium Python 包中的环境。
-
在一次彻底而易于理解的深度探讨中,Nicolas Arroyo Duran提出了一种训练生成式机器学习模型的新方法,该方法能够逼近任何具有多变量输出的随机函数。
-
对于任何有兴趣了解前沿 RAG 方法的人,Steve Hedden的最新实践指南提供了一个耐心的、逐步的 Graph RAG 实现流程,使用知识图谱和向量数据库。
-
你应该如何设计一个“入门”AI 项目,特别是在那些尚未采用这项技术的公司中?Julia Winn提供了具体的建议,帮助产品经理拓展新的领域。
-
以经典的“额头侦探”猜谜游戏为出发点,Krzysztof K. Zdeb分享了他与 LLM 一起玩该游戏的实验结果,并展开了关于模型当前推理能力的更广泛讨论。
-
如果您经常处理地理空间数据,并希望增长您对可用工具和流程的知识,Amanda Iglesias Moreno 向我们展示了如何通过 Overpass API 提取地铁路线数据。
感谢您支持我们作者的工作!正如我们之前提到的,我们热衷于发布新作者的文章,所以如果您最近写过关于我们核心主题的有趣项目演示、教程或理论反思,千万不要犹豫,与我们分享。
直到下一个变量,
TDS 团队
数据网格注册表 — 进入数据网格的窗口
在不断发展的企业数据网格生态系统中查找数据产品是一项挑战,但传统的集中式数据目录可能不是答案。相反,数据网格的联邦式方法为创建进入企业数据网格的窗口提供了新的机会。
·发布于Towards Data Science ·阅读时间 20 分钟·2024 年 1 月 20 日
--
图片来自Nathan Anderson 于Unsplash
数据网格注册表 — 进入数据网格的窗口
传统的数据目录是在没有简单方法搜索和查找分布式数据景观中的数据时构建的。元数据被移到一个可以以一致方式存储的位置,然后构建了可以搜索该目录存储库以找到所需数据的应用程序。实际上,传统的数据目录提供了查找和使用企业数据所需的“智能”。
但是,数据网格提供了一种替代方案。数据网格中的数据产品已经拥有并维护其元数据。它们具有明确的边界、一致的访问机制和自助服务能力。实际上,它们已经具备了查找和使用数据产品及其内部数据所需的“智能”。因此,实际上,我们需要的是一种不同的……
数据投资回报率金字塔:衡量和最大化数据团队价值的方法
难以清晰表达你数据团队的价值?了解如何使用数据投资回报率金字塔计算你数据团队的回报。
·发表于 Towards Data Science ·阅读时间 12 分钟·2024 年 2 月 2 日
--
图片来源:作者
直到一年前,我与约一半的数据领导者交谈时,他们认为自己团队的业务价值几乎是自明的。而今天,最大化和衡量数据团队的投资回报率已成为每位数据领导者议程的重点之一。
大多数数据团队的投资回报率公式都专注于以下某个版本的计算:
提升 / 投资 = 投资回报率(ROI)。
尽管其简洁性无疑具有价值,但它并没有完全捕捉到数据团队的全部价值。例如,如何衡量以下几点的价值:
-
客户流失率仪表板
-
支持即席查询用户参与行为的数据集
-
迁移到一个支持更快速、更可扩展计算的新数据架构
-
由于数据质量改进措施,数据采纳率提高了 30%
这并不容易!那些成功将客户获取漏斗这一复杂的领域转变为可预测科学的资深数据行业专家,在向内审视时,往往会感到不安。
在过去的六个月里,我与数据领域的领导者进行了交流,并反复探讨了各种投资回报率公式,所有这些努力都是为了达成一个目标:即使无法完全捕捉到数据团队的确切价值,至少能够更接近这个目标。
而这些讨论的结果就是一个全新的数据 ROI 金字塔。是的,我知道之前有很多伟大的金字塔和联结三角形,但这个不同,算是不同吧。
这个金字塔的目标明确地帮助数据领导者:
-
更接近业务
-
平衡相互竞争的优先事项
-
并专注于正确的指标,为其利益相关者创造价值。
所以,前言已经结束,让我们从头开始!
计算数据 ROI
图片由作者提供。
一般来说,组织中职位越高,你的指标就会越少且越全面。CEO 并不关心你支持多少个仪表盘,或者你的数据新鲜度 SLA 遵守百分比。
他们想知道他们的投资者想知道的:“我有没有从我的投资中获得回报?”
数据 ROI 金字塔通过与引言中类似的公式来解决这个问题:
(数据产品价值 — 数据停机时间)/ 数据投资 = ROI
……但有两个关键区别。第一个是“数据产品收入”的定义更广泛(稍后会详细介绍),第二个是引入了数据停机时间。
停机时间变量很重要,因为随着更多数据团队通过机器学习模型、面向客户的应用程序、数据民主化和其他举措推动更高的收入水平,停机时间的后果在时间、收入和信任方面变得更加严重。
这也使得减少数据停机时间成为数据领导者提高 ROI 的三大战略之一:你可以增加收入,可以减少投资,或者可以减少数据停机时间。而其中一个策略比其他的更容易实现。
所以,现在我们已经有了计算 ROI 的框架,让我们深入探讨如何识别这些变量。
计算数据投资
图片由作者提供。
公式很简单 —— 投资 = 人员 + 解决方案。
但也很容易过于复杂化。
一些合同是年度的,其他的则不是。有些解决方案按使用收费,其他的则不收费。我在这里的建议是保持这个部分相对简单。坚持对成本进行总体预测,并均匀分配到一段时间内(通常是一个月或一个季度)。
优化数据投资的杠杆
在优化数据投资时,关键是效率。为了最大化数据投资的价值,你需要提高这些投资带来价值的速度。
以下是你可以利用的三种杠杆,来提高你数据系统、数据团队和数据消费者的效率。
-
系统优化 — 几乎所有现代数据解决方案的成本都基于使用量。你需要关注的指标是驱动这些成本的项目总数(如表格、查询、虚拟机、数据仓库等),以及顶部的异常值(高成本查询)或底部的异常值(未使用的表格)。理解和控制系统成本的几种方法可能包括为领域分配所有权、清理未使用的资产和高成本查询,甚至是围绕中央工具整合数据堆栈。
-
构建和维护时间 — 构建和维护关键数据资产(包括数据产品和机器学习能力)所需的时间是衡量数据团队生产力的一个关键杠杆。尽管开发一个有效的数据平台可能需要较大的前期投入,但简化数据管道的构建和维护工作流,可以显著提高数据团队的效率。
-
洞察(或行动)时间 — 这个杠杆关注的是数据消费者实现价值所需的时间。换句话说,数据团队在多大程度上有效地帮助了数据消费者?可发现性和自助服务可以改善消费者的洞察时间,而微批处理基础设施则可以以最低的延迟使数据可用于机器学习、分析和报告。
花费时间来支持数据自助服务通常是值得的,但其回报必须大于构建和维护所投入的努力。图片由Shane Murray提供。
计算数据产品回报
图片来源:作者。
计算数据产品回报是我们 ROI 计算中最复杂的步骤。因为随着数据行业的不断发展,数据产品使用案例的多样性和复杂性也在不断进步。
幸运的是,数据产品通常可以分为三大类:分析数据产品、操作数据产品和面向客户的数据产品。这些产品可以以仪表板、机器学习模型、利用数据洞察的实验形式存在,当然还有——生成式人工智能。虽然后者可能在构建上稍显复杂,但生成式人工智能本质上仍然是数据产品,并且它的价值仍然可以通过我们将在下面概述的方法来计算。
要真正回答这个问题,数据产品回报的公式需要包含所有数据团队的活动,无论它们是直接创造价值(例如通过付费墙机器学习模型产生的收入),还是间接创造价值(例如客户流失仪表板)。
我建议用来计算数据产品回报的公式是:
分析数据产品 + 操作数据产品 + 面向客户的数据产品 = 数据产品回报
如你所见,这部分的方程式为我们的 ROI 计算带来了最多的变量。由于这一部分是迄今为止最复杂的,我们将在这里花费大部分时间。因此,考虑到这一点,我们将更加详细地查看每个子类别(或使用案例)。
分析数据产品
首先,让我们深入探讨最传统——也是最常见——的数据使用案例:分析。
分析数据产品是由你的数据团队支持和执行的关键仪表板、机器学习模型和实验的组合,用于为决策提供洞察。
无论我们谈论的是营销仪表板还是像客户生命周期价值(LTV)这样的关键指标,分析数据产品在任何业务的日常运作中都扮演着基础性角色。但就像所有数据产品并非一视同仁一样,你计算它们的价值方式也不会相同。当然,有些计算起来会比其他的容易。
首先,让我们看看实验:
衡量增量影响
衡量绝对影响是理解任何数据产品回报的最简单方式之一。通过理解测试与对照组之间的差异,并将这些数字转化为每月赚取/节省的美元,你可以快速估算由数据团队的研究和分析洞察所进行的实验的价值。
对于更保守的方法,你可以通过计算一个随机或平均决策的回报来估算价值,以更好地代表没有数据团队支持下所做的决策。
每年结合数十个或数百个这样的实验,将为你提供一个大致的数字,来衡量实验平台所带来的增量价值以及围绕这些实验的分析工作。
衡量对利益相关者的价值
那么仪表板呢?这些项目很少能通过控制实验或自然实验来轻松衡量。
为了考虑这些数据产品,我们需要采取更为细致的方法来估算价值。在这种情况下,我们将通过与消费者本身接触,将定性数据转化为有代表性的东西。
不管你信不信,你的业务用户和数据消费者实际上对你的仪表板对他们的价值(或不价值)有着相当的了解。而他们的反馈是可以量化的。虽然乍一看,这似乎并不够严谨,但这实际上与麻省理工学院经济学家所使用的方法类似,用来确定免费的服务如何为国家 GDP 贡献价值。例如,他们问受访者,如果一年内不使用 Facebook 或 Google Maps,愿意接受多少报酬。(如果你感兴趣的话,Facebook 的月费用大概是 40 到 50 美元。)
对于最重要的仪表板,数据团队可以进一步通过为响应者创建基准来进行衡量,例如:“我们估计上个季度维护此仪表板的成本约为$5,000。您认为在那段时间内,它为您的工作增加了相同的价值吗?”作为基准,以下是我们对200 名数据专业人员的调查结果,显示他们认为数据消费者会如何评估他们的仪表板:
-
少于$500k: 5%
-
500k-1m: 11%
-
1m-10m: 49%
-
10m-25m: 32%
-
25m+: 5%
面向客户的提升
在这里,我指的是特别针对客户的面对面数据,而不是由数据驱动的机器学习模型。这种数据用例通常有两种形式。
第一个情况是数据本身就是产品。许多企业会摄取、转换并出售数据给其他公司。例如,一家数据挖掘公司通过抓取电商网站获取见解,或一家电视制造商将收视数据出售给广告商。
在这种情况下,计算非常直接:数据产品的收入就是销售收入。当你找到丰富数据的方法时,你使其变得更有价值,从而提高销售价格。
那么,数据仅仅是所提供产品的一部分时该如何处理呢?例如,一个销售点系统提供有关商户人流模式的见解?或者一个视频播放器根据时间细分观众群体的观看情况?
在某些情况下,数据可能只是锦上添花。在其他情况下,它可能是客户获取和保持的一个重要因素。幸运的是,数据团队已经开始实验并衡量功能对客户保持的影响一段时间了。
图片来自Shane Murray.
操作性提升
我将操作数据用例定义为必须发生的活动。例如,向董事会报告或航空公司为延误航班的乘客重新安排座位等。
如果数据系统出现故障,这些活动仍然会发生,但会变得更加痛苦。组织可能需要手动收集并汇总来自整个业务的数据进行报告,或者乘客可能需要去客户服务台,而不是通过应用程序自动展示他们的重新安排行程选项。
在这些情况下,价值通常最好通过自动化和痛苦过程之间节省的时间来确定。在某些情况下,诸如避免罚款或降低客户满意度等替代影响也可以进行计算。
图片由Shane Murray 提供。
最大化数据产品回报的杠杆
你可以通过提高数据产品的有效性和其影响范围来优化数据产品的价值。一些可以在广泛的使用场景和行业中衡量的宽泛指标包括采用率、覆盖面和速度。
-
采用率和覆盖面——数据产品的使用越多,它能提供的价值就越大。因此,推动更好的覆盖面和采用率也能显著增加你的数据产品所能提供的增量价值。
-
速度——实验价值的最大驱动因素之一是速度:组织在一段时间内能执行多少有意义的实验?更高的速度意味着提高生产力、更成熟的中央平台,甚至是更好的数据消费者支持。
计算数据停机时间
图片由作者提供。
最后,我们需要了解数据停机时间如何影响 ROI。
在其他文章中,我们讨论了如何使用事件和响应时间来计算数据停机时间。以下是该公式的样子:
事件数量 x (检测平均时间 + 解决平均时间)
这对于衡量你整体数据产品可靠性的趋势是有帮助的。但在这种情况下,我们暂时不关心整体数据停机时间或团队的效率(尚且不考虑)。
我们在这里想要找出的,是特定数据产品的数据停机时间的运营成本。为此,你需要数据血缘关系,以便理解上游表格的数据问题如何影响各种下游数据产品。
既然我们已经计算出每个数据产品(包括关键仪表板)所产生的收入,我们现在可以从收入中扣除这些停机时间的运营成本。
对于这个 ROI 计算的组成部分,我建议只关注违反数据服务水平协议(SLA)的停机时间。如果一个每天检查的仪表板出现数据新鲜度问题,并且该问题在几小时内就得到解决,那么这种停机时间不太可能对组织产生运营影响(你的数据团队也不应因此受到惩罚)。
减少数据停机时间的杠杆
数据停机时间的改善可能对你数据团队的 ROI 计算产生巨大影响,尤其是在数据对产品提供至关重要的用例中,数据停机时间等同于运营停机时间。
Red Ventures 定制构建的 SLA 遵守仪表板。图片由Red Ventures 的 Red Digital 提供。
我们已经讨论了 CEO 关心的指标以及数据领导者应考虑的战略指标。现在让我们来谈谈你的团队可以采取哪些战术杠杆,以在这些战略指标上最大化你的成果。
SLA 遵守情况
SLA 遵守率(SLA 被违反的时间与 SLA 被遵守的时间的比例)可以帮助数据团队维持对特定数据资产受到的不良影响及其数据质量事件的详细了解——并采取措施保护这些产品的价值。
覆盖率%
最小化数据停机时间往往可以与数据产品从原始数据摄取到最终结果表的监控和自定义测试覆盖程度直接相关。数据管道和系统之间的依赖性极强。更高的覆盖率通常意味着更短的检测和解决时间。
状态更新%
那些在记录事件历史方面做得最好的团队几乎总是拥有最低的停机时间。高状态更新比例有助于缩短解决时间,甚至通过提供更好的数据健康状况洞察来预防事件的发生。
如果你的数据团队有较低的状态更新比例,那要么是因为警报疲劳,要么是因为你的团队无法迅速处理事件。
计算数据 ROI 很难——但这是值得的
虽然我并不认为计算数据产品的投资回报率(ROI)是一门精确的科学或快速的成功之道,但我坚信这是一个值得追求的努力——也许最重要的是,这是一个可以实现的目标。
我也相信,对于数据领导者来说,很少有比这更关乎生死存亡的活动了。
通过更好地量化和优化数据团队的价值,我们可以更好地赢得同事们的信任,并获得他们对我们为业务增值的认可。到那时,对于那些有动力、领导得当的数据团队来说,天空才是极限。
那份让我获得工作和面试机会的数据科学简历
数据科学简历的结构解析
·发布于 Towards Data Science ·阅读时长 8 分钟·2024 年 6 月 23 日
--
照片来源:Resume Genius 于 Unsplash
在这篇文章中,我将带你走过我那份成功获得多个数据科学面试和工作机会的简历,并分享一些我最重要的建议!
简历一览
如果你没有时间阅读整篇文章,以下是我的简历/CV 的完整内容。
我的简历/CV,作者提供。
出于隐私原因,我已经对某些部分进行了匿名处理,但这真的是我在申请数据科学职位时所使用的简历结构和语言。
如果你想获得这个 PDF 模板,请查看下面的链接!
数据科学家选择数据供应商的指南
有效评估和选择数据以丰富和改善你的模型的实用指南
·发表于数据科学前沿·6 分钟阅读·2024 年 6 月 28 日
--
一位数据科学家在数十个数据供应商之间做选择 | imagine.art
在过去的五年里,我曾在两家上市公司担任数据科学、人工智能和研究的副总裁。在这两个职位上,人工智能都是公司核心产品的中心。我们与数据供应商合作,利用相关特征丰富我们的数据,从而提升我们的模型性能。经过与数据供应商的多次合作后,这篇文章将帮助你在测试新供应商时节省时间和金钱。
警告: 在你拥有非常清晰的商业指标,并且已经投入相当时间优化模型之前,不要开始这个过程。与大多数数据供应商的首次合作通常是一个漫长的过程(最好的情况是几周,但通常需要几个月),而且可能非常昂贵(我合作过的一些数据供应商每年的费用高达数万美元,其他一些则在大规模运营时每年费用达到数百万美元)。
由于这通常是一次巨大的投资,除非你能够清晰地制定出如何做出“继续”/“停止”决定的过程,否则不要开始这个流程。 这是我见过的第一大错误,所以请再读一遍这个句子。对我来说,这通常需要将所有的……
数据演讲者的蓝图:将分析转化为掌声
我作为数据科学领域的公众演讲者的经验与学习。
·发布于Towards Data Science ·7 分钟阅读·2024 年 1 月 21 日
--
我在 2023 年柏林的 Applydata Summit 大会上
在我的数据科学家生涯中,我参加了许多会议和聚会。当我刚开始工作时,很多人还在为理解什么是数据科学以及如何利用不断发展的云计算解决方案而苦恼。那时总感觉像是在丛林中求生!
这就是为什么通过聚会和其他本地活动与数据社区建立联系变得对我至关重要。在某个时刻,有人邀请我展示我正在做的一个项目。那时我才意识到,我是多么喜欢分享和解释我的工作!
从那时起,我有幸在多个会议上发言,包括ODSC、PyCon和Data Innovation Summit等。每次我都会被问到:
“你是如何为会议找到合适的故事的?”
“你在展示技术内容时,不怕犯错吗?”
这些问题,以及我收到的其他类似问题,让我意识到是时候分享这些年来我所学到的东西,并希望能够激励新的数据专家分享他们的知识。
为什么我们需要数据演讲者?
数据真的很难!公司往往不知道他们真正需要什么,这迫使数据爱好者去为一些有时根本无法解决的问题找到解决方案。现在,想象一下,与一个面临相同挑战的人建立联系,并可能提供你一直在寻找的答案,是多么有帮助啊。或者,或许只是找到一个你能够产生共鸣的人,可以和你一起分享并深入探讨你正在做的工作。
这正是为什么我们在这个领域需要优秀的沟通者。我仍然清楚地记得那位了不起的数据工程师在演讲中分享了他如何解决 AWS Lambda 部署问题的经历。这个问题困扰了我几个星期。
另一方面,听别人展示一个用例可以是一次启发。它是一种发现某些解决方案并理解如何应用它们的方式。此外,如果你对自己的工作充满热情,展示你的想法并开放讨论是非常愉快的。这是一个从各方听到反馈的机会。
我也坚信,在 AI 技术,如应用的大型语言模型(LLMs)取得显著进展的今天,沟通的需求愈加迫切。这对于解释我们与技术之间的复杂层次至关重要,而这些技术已经成为我们生活的一部分。
我没有什么可说的
由 Dall-E 生成
可惜,这种情绪我常常遇到。在我看来,它总是像这样回响:
“我没有发言的权利,因为我没有什么可说的。”
但事实远非如此。总是有话可说,特别是当你每天都沉浸在数据的世界中时。你被挑战和故事包围,这些挑战无法通过简单的确定性解决方案来解决。对我而言,这正是做出非凡成果的完美起点!
我们有时会忽视自己工作的意义,仅仅因为它是我们日常对话的一部分。我们与老板、同事讨论它,却忘记了在我们的圈子之外,许多公司和个人仍在摸索数据和 AI 的基础知识。例如,我知道有很多人并不熟悉 ChatGPT,尽管它越来越受欢迎。把你的见解带到更广泛的圈子,你会意识到你的受众有多广泛!
最后,如果你从事数据工作,那么你本质上就是一个讲故事的人。要在这个领域工作,无法避免将复杂的背景转化为更易理解的叙述。你可能是在下意识地做这件事,但你一定是在做。
演讲作为产品:技巧与窍门
考虑将你的演讲打造成任何其他产品一样。它的价值至关重要;没有价值的演讲可能就不值得进行。
具体来说,向会议展示内容是一项协作努力,涉及听众、演讲者和组织者。如果你的演讲没有为这三大关键群体增值,那么回过头来重新评估它的目的会更明智。
我相信,一场引人入胜的演讲始于解决一个特定的需求。它可能是你投入了数月时间的成果,最终意识到它值得分享。无论是关于你成功的解决方案,还是你失败的经历(以及从中学到的教训),都应该来源于你的个人专业知识和勤奋努力。
在深入探讨一些制定演讲技巧之前,让我们先解决我认为的“房间里的大象”问题:
目标是成为数据倡导者,而非数据大师。
我观察到,世界上充斥着“导师”,但真正的专家寥寥无几。当你考虑登上讲台时,要将其视为一个分享知识的平台,而非一个目的地或巅峰成就。“导师”这个词本身并不带有负面含义,但在此语境下,我想强调我所做的区分。
找到主题
在决定下一个演讲的主题时,我首先会提出三个基本却至关重要的问题:
-
我目前正在从事什么项目?
-
我在这项工作中最近面临了哪些挑战?
-
分享这些信息对他人有益吗?
这些问题是我确定理想主题的起点。接下来的步骤是研究其他人是否在讨论类似的主题,渠道可以是出版物、演讲或 Medium 文章等。这个阶段至关重要,因为它需要对当前趋势和发展有透彻的了解,从而确保我的贡献以某种独特的方式脱颖而出。
例如,几年前我写过一篇 Medium 文章。我并不是在介绍一些全新的东西;创新之处在于我如何将现有的技术结合起来,解决一个特定的挑战。这个经历随后成为我在汉堡当地 Python 社区演讲的核心内容。
制作你的演示文稿
图片来源:作者
我坚信“少即是多”的理念,尽管我对不同观点保持开放态度,但我在这个信念上相当坚定!与其过度依赖文字,我建议使用简洁的视觉效果来解释你的算法,或者采用简短的要点作为指导。当你面对的观众可能对主题不如你那样熟悉,并且很可能已经听过其他类似演讲时,过多的文字会适得其反。结果是什么呢?观众失去兴趣,且感到你的信息未能传达出去。试想一下,在这种情况下,听一场 30 分钟的演讲会是怎样的体验!
在演示文稿中保持简约,展示仅能补充你讲述内容的关键信息。要明白,无法覆盖每一个细节。因此,为那些有兴趣进一步探索主题的观众提供一些相关链接。
记得在演讲中要承认他人的工作。给原创内容以应有的赞誉非常重要,因为这并非一场竞争。通过认可他人的贡献,你可以显著提升演讲的质量,展示你对主题的深刻理解。
解释得像没人知道你在说什么
这是你真正脱颖而出的时刻,毕竟你为你的主题付出了那么多努力!目标是吸引所有人的注意力。确保在场的专家对熟悉的主题感兴趣,而那些知识较少的人也能感到被包容,并能理解你的要点。要留心你的观众,观察他们是否跟得上你的节奏。如果时间允许,可以用一个笑话来轻松一下气氛,缓解你和他们的紧张情绪。在演讲过程中,直接向观众提几个问题,保持他们高度关注。
我把这看作是一场战略游戏,你必须积极防止观众的注意力下降。尽可能加入更多的细节,帮助观众与你以及你解决的问题产生联系。这可能包括一些关于你的公司背景,或者在你的解决方案之前采用了哪些方法。
这就是为什么我们称之为讲故事,而不是讲数据。
最终,会有一个问题出现:你是否应该将演讲稿背下来并记住它?在我看来,这是个人的偏好问题。就我个人而言,我倾向于不这么做。主要是因为我喜欢让思路的流动引导我的演讲,这样我可以自发地加入一些原本没有考虑到的想法。这种方式,主要通过经验驱动,让整个过程变得更加愉快,至少在我看来是这样。
最后的思考
由 Dall-E 生成
对某些人来说,可能最令人生畏的是站在舞台上,但对我来说,真正的挑战是与冒名顶替综合症作斗争。这种感觉似乎是数据科学家们普遍经历的。因此,走上讲台谈论一个稍显模糊的项目,或者一个未按预期顺利进行的项目,可能会非常具有挑战性。幸运的是,我克服了我的忧虑,感谢在我旅程中遇到的无数杰出演讲者,我现在可以回顾我的成就,并认识到许多确实值得分享。
所以,跳出你的舒适区,找到最适合你的舞台。无论是数据科学的主题,还是数据工程的案例,收集你所有的见解,走出去,与世界分享。
如果你需要支持或想要联系,可以随时访问:www.aromano.dev/
数据战略选择级联
揭开数据战略的面纱
你的数据战略应是什么样子
·发布于 Towards Data Science ·19 分钟阅读·2024 年 9 月 16 日
--
这是一个系列文章的第一部分,在其中我们将揭示数据战略的奥秘——这是任何旨在成为数据驱动、在当今数字化世界中保持竞争力的组织必不可少的组成部分。 使用本文提供的备忘单来启动你自己的数据战略开发!
tl;dr
-
当组织努力成为数据驱动时,他们需要进行转型,这需要一个坚实的数据战略设计。
-
著名的《赢得胜利》战略框架提供了一个过程来开发任何类型的战略,并提供了记录和沟通战略的格式。
-
本文展示了如何将企业战略设计中的战略选择级联方法应用于数据战略的开发。
-
本文提供的备忘单可以帮助数据战略设计团队快速应用经过实践验证的框架,从而帮助组织成为数据驱动的。
目录
· 1. 动机与背景
∘ 1.1 数据驱动的公司
∘ 1.2 挑战在哪里?
· 2. 数据不是已经在 IT 战略中处理了吗?
· 3. 设计数据战略时应使用什么战略框架?
∘ 3.1 《赢得胜利》框架
∘ 3.2 工具 1:战略过程图
∘ 3.3 工具 2:战略选择级联
∘ 3.4 《赢得胜利》框架在数据战略设计中的应用
· 4. 数据与业务战略的关联
· 5. 数据战略选择级联
∘ 5.1 将选择级联转换为数据与人工智能
∘ 5.2 数据战略选择级联
∘ 5.3 数据战略选择层级备忘单
∘ 5.4 何时使用数据战略选择层级
· 6. 两个数据战略示例
· 7. 结论与展望
· 参考文献
1. 动机与背景
1.1 数据驱动型公司
无论公司大小,或所在行业如何,都在努力实现数据驱动。无论使用何种技术或如何命名——数据科学、商业智能(BI)、高级分析、大数据、机器学习或(生成性)人工智能(AI)——数据驱动是通过将数据作为公司战略资产来提升价值。
数据驱动的重大承诺是:
-
人类做出更好的决策:各级管理人员和员工能够通过将数据和分析与人类常识结合,始终做出更好的决策。
-
计算机进行自动化决策:运营中的频繁和重复决策可以实现自动化。
反过来,这通过以下方式带来了长期的竞争优势:
-
更多的收入
-
降低成本,提升效率和利润
-
更好的风险管理
-
更多的创新和基于数字产品与服务的新型商业模式的可能性
这些好处通过单个的数据使用案例实现。一些具体的例子包括:
-
针对 B2C 业务的客户保持:可以通过人口统计或市场数据来丰富客户数据,然后利用这些数据构建统计模型(如果你喜欢,也可以称之为机器学习或人工智能),以学习规则,预测客户流失的概率。这意味着,对于每一个单独的客户,会计算出一个 0%到 100%之间的概率,表示该客户是否可能在下个月终止合同。结合可以从数据中推导出的客户(生命周期)价值,客户服务中心可以利用这些信息做出有根据的决策,确定应该联系哪些具体客户,以防止失去“优质”客户。对于拥有大量客户且服务中心资源有限的公司来说,这通常会显著减少客户流失,从而在长期内实现可衡量的收入增长。
-
端到端产品利润分析:财务和产品数据,以及明确的成本分配规则,可以用于开发一个商业智能仪表盘,分析单个产品的利润。仪表盘用户可以浏览数据,并从不同维度进行深入分析,如国家、销售点、时间或客户类型。这使得业务用户能够做出有根据的决策,决定哪些产品可能从现有产品组合中移除,从而提高利润。
-
预测性维护以优化生产:传感器数据,如温度或振动,可以用来预测机器在不久的将来发生故障的概率。凭借这样的洞察力,维护团队可以主动采取措施,防止机器发生故障。这可以减少维修成本以及生产停机时间,从而提高收入。
成为数据驱动型的好处似乎不言而喻,而且似乎普遍认同对于大多数公司来说,这已经是不可避免的。来自1的引用精准地说明了这一点:
“通过数据实现一致的价值创造,今天已经是企业的决定性竞争优势,且在不久的将来,它甚至将成为生存的关键。”1
1.2 挑战在哪里?
如果成为数据驱动型的普遍好处显而易见,那么问题在哪里呢?为什么不是每个组织都是数据驱动型的?
事实上,尽管这个话题并不新鲜——商业智能、数据科学和机器学习已经存在了几十年——许多公司仍然难以充分利用他们的数据,并且距离成为数据驱动型企业还有很长的路要走。这怎么可能呢?
一开始需要明确:在我看来,真正的挑战并非技术问题,尽管许多技术提供商喜欢声称是技术问题。当然,技术是基于数据做决策的基础——如今几乎所有与商业相关的活动都依赖于此。IT 系统、工具和算法已经可以随时用于执行基于数据的决策制定。为组织配备合适的工具是一个复杂但不复杂的问题[16],可以通过正确的知识或支持得到充分解决。那么,是什么阻碍了组织持续创新、测试和实施数据使用案例——像上面提到的那些——以将数据作为战略资产呢?
为了成为数据驱动型,员工在日常业务中识别、处理和使用数据的方式需要发生根本性的变化。
成为数据驱动型意味着,每个部门的员工都必须能够将关键的商业问题转化为可以通过数据和分析解决的分析问题。组织中需要有一种(数据) 文化3,确保员工愿意、能够并且必须使用数据来改进他们的日常工作。然而,改变更多人的行为永远不是一项简单的任务。
“组织需要转型,才能成为数据驱动型。”2
所以真正的挑战是可持续地改变一个组织中决策的方式。这不是通过设计和实施一个项目就能完成的,而是需要一次转型。
将一个组织转型为数据驱动型是一个复杂的——而非复杂的——挑战。这需要一个坚实的战略基础——数据战略。
然而,许多组织并没有数据战略,要么难以制定,要么担心这将是一个漫长的过程。通过这篇文章,我们希望解决这个问题,揭开数据战略设计的神秘面纱,并为读者提供必要的工具,使他们能够使自己的数据战略获得成功。
2. 数据不已经在 IT 战略中处理了吗?
大多数组织通常都有企业战略、IT 战略甚至数字战略。那么,为什么还需要一个专门的数据战略呢?
如果您的组织拥有任何战略,并且该战略充分深入地将数据作为资产进行处理,那么您就没问题。充分深入意味着,关于数据价值创造的基本问题已经被详细阐述并解答,组织可以采取具体行动,利用数据作为资产。然而,根据我的经验,这种情况很少见,许多组织在如何理解和区分“数据”、“数字化”和“技术”这几个术语上往往缺乏共同理解。
虽然数字战略通常侧重于流程数字化或为内部员工或外部客户设计数字解决方案,但 IT 战略往往关注系统架构、应用程序和网络基础设施。这两者与数据的生成和利用密切相关,但无论是数字战略还是 IT 战略,在核心上都不专注于使组织成为数据驱动型。
该信息图提供了关于数据、数字和技术术语的更详细比较:
图 1:数据、数字和技术术语的比较和对比。由作者制作的信息图,最初发布于[10]。
因此,对于每个组织来说,首先评估现有战略、举措和相应领导者已经涵盖了哪些数据相关的主题是非常重要的。然后,如果现有战略没有详细回答如何利用数据作为资产的问题,就该创建一个专门的数据战略了。
3. 设计数据战略时使用哪个战略框架?
战略本身可能是最被误解的概念之一[14]。有许多定义和解释。因此,似乎并不存在唯一的正确方法来设计数据战略。那么,应该从哪里开始呢?
3.1 Playing to Win 框架
我们在这里提议选择一个经过验证的通用框架,可以用来制定(任何类型的)战略,并将其应用于数据战略设计。为此,我们使用了“Playing to Win” (P2W) 战略框架4,该框架源自前宝洁公司首席执行官艾伦·G·拉夫利(Alan G. Lafley)与罗杰·马丁(Roger Martin)的联合研究,后者在开始开发该框架时曾在 Monitor Consulting 工作。
这一方法成为宝洁(P&G)公司的标准战略方法,并已成功应用于许多行业。此外,P2W 框架还通过 Roger 的一系列战略实践者见解不断得到补充和完善[15,17]。
选择 P2W 方法的好处在于,它广泛为人所知并得到应用,并且拥有一个完整的生态系统,包括可以用于设计数据战略的流程、模板和培训资源。
在 P2W 框架中,战略定义如下4:
“战略是一个一体化的选择集,能够独特地定位公司在行业中的位置,从而相对于竞争对手创造可持续的竞争优势和卓越的价值。”4
因此,战略就是关于选择,做出这些艰难的决策为你提供了竞争优势,但无法确保这些选择最终一定是正确的。请注意,这与将战略视为某种计划[9]的做法有很大不同。
P2W 战略框架归结为两个核心工具,用于战略设计。
3.2 工具 1:战略流程图
战略流程图是一系列指导战略设计过程的步骤[5,6]。本质上,它帮助创新多个场景或所谓的可能性,描绘你的战略可能的样子。然后,这些可能性会被评估和比较,以便战略团队选择最具潜力的可能性作为最终战略。
它采用严格的评估方法。这有助于为每一个可能性创建清晰的视角,明确这些假设必须成立,才能将某个可能性视为一个好的战略。
战略流程图由 7 个阶段组成,可以按如下方式可视化:
图 2:IDEO U 的战略流程图,来源于[6]。
3.3 工具 2:战略选择级联
战略选择级联是第二个工具,用于记录战略的核心组成部分,它作为战略工作成果并为向利益相关者有效传达战略提供了一个良好的视觉呈现方式[7,8]。它由五个元素组成,通常呈现为类似瀑布的形式:
图 3:战略选择级联
战略选择级联的这五个元素定义为:
-
获胜愿景:定义你组织获胜的标准。
-
如何竞争:你将选择在哪些领域进行竞争,哪些领域不进行竞争。通常,这一元素包括五个维度:i) 地理位置,ii) 客户,iii) 渠道,iv) 产品/服务,v) 生产阶段。
-
如何获胜:你的竞争优势,你将如何可持续地赢得客户。这归结为降低成本或差异化。
-
必须具备的能力:建立竞争优势所需的能力。
-
支持管理系统:基础设施(系统、流程、度量标准、规范和文化),这是有效执行该战略所必需的。
战略的核心由第二和第三个框中做出的连贯选择构成:在哪里竞争 & 如何取胜。
级联框中的每个框代表一个主题,对于这些主题,我们之前提到的艰难选择必须做出决策。
级联如何运作最好通过一个实际案例来说明,以下实例来自[9],描述了西南航空公司企业战略的选择。
图 4:西南航空公司战略选择级联示例
在战略设计过程中,战略选择级联不仅仅是填充一次,而是在战略流程图的多个阶段中反复使用。例如,在创新阶段产生的每一个战略可能性,都可以用级联的方式来描述,以便在战略团队内建立共同的理解。此外,组织当前的战略被描述为战略设计过程的起点,以便建立对现状的共同理解。
3.4 数据战略设计的“从竞争到取胜”框架
P2W 框架不仅限于企业战略设计,还可以用于制定任何类型的战略,例如公司部门、职能(如 IT、营销或销售)甚至个人的战略。
因此,它也可以用于设计数据战略。那么,选择 P2W 框架来设计数据战略为什么是一个特别好的选择呢?
-
数据战略设计团队往往直接跳入细节,例如选择什么样的组织设计(中央化、去中心化或中心-辐射型),或者应用什么技术(数据网格、数据架构、湖仓)。这些都是最后一个框(支持管理系统)的一部分,当然需要解决,但不是第一步。初步的重点应放在数据战略的核心,即“在哪里竞争”和“如何取胜”。P2W 框架帮助我们退一步,集中精力回答战略性问题。
-
由于 P2W 框架已经得到广泛应用,你的组织很可能已经在使用它。如果是这种情况,将数据战略与其他现有的关联战略整合起来是直接可行的(见第四部分),并且有助于向相关利益相关者传达数据战略,因为他们熟悉这种方法论。
-
一些数据战略方法给我的感觉像是从最佳实践中挑选选择,或者按照检查清单解决所有必需的元素,缺乏真正的战略思维。P2W 框架引导并迫使战略设计团队严谨而富有创造力地运用战略思维。
尽管将 P2W 框架应用于数据战略设计的理念既不是高深的技术,也不算新颖,但现有文献[e.g. 12,13]——根据作者的了解——并未提供如何将该框架直接应用于数据与人工智能战略设计的详细描述。
在本文的其余部分,我们将战略选择级联模型应用于数据战略设计,为数据战略设计团队提供了应用 P2W 框架所需的工具,以便轻松地进行战略工作。这将产生对数据战略的清晰定义,明确数据战略的构成内容,以及如何记录和传达它。
4. 数据与商业战略的联系
在公司中应用数据科学、人工智能或任何其他与数据相关的技术,并不是目的本身。组织最好不要在没有明确业务需求的情况下,启动一个可能带来痛苦的公司转型计划以实现数据驱动(尽管我看到这种情况发生的比预想的更为频繁)。因此,在开始设计数据战略之前,必须有充分的理由证明,成为数据驱动是公司生存的好主意,甚至是必不可少的。这通常被描述为数据与商业战略之间的联系。
好消息是,在使用 P2W 框架进行数据战略设计时,通过将数据管理、分析或人工智能作为公司战略中的必备能力,能够为数据与商业之间建立自然的联系,从而帮助组织取得成功。
一个相关的例子可以在[7]中找到,其中制定了 OŪRA 公司战略的战略选择级联模型。该健康技术公司生产一款手环,类似于智能手表,可以捕捉身体数据。其能力定义包括以下内容:
图 5:OŪRA 公司战略中的能力子集[7]。数据管理与分析被定义为必备能力,以实现胜利。
从中可以看出,最先进的数据管理和数据分析能力的需求显而易见。
由于在几乎所有行业中,数据驱动的益处都存在,数据管理、分析或人工智能如今不仅是高科技公司必须具备的能力,如上述示例所示。像制造业、金融、能源与公用事业、化学品、物流、零售和消费品等行业也越来越多地利用数据与分析保持竞争力。
无论你的公司所在的行业是什么,它理应已经认识到成为数据驱动的重要性,并将其作为公司战略或组织内部其他战略(如销售或营销战略)的组成部分,在开始数据战略工作之前进行表达。
然而,在许多情况下,业务和数据之间并没有明确的联系。在这种情况下,重要的是要花足够的时间,识别出数据战略需要解决的根本业务问题。这通常在战略流程图的第一阶段完成(见图 2)。
确定数据战略的战略目标问题的一种方法是通过与相关的业务和管理利益相关者进行一系列访谈来逐步剖析问题。这使得我们能够从业务角度以及与组织中数据管理和使用相关的角度识别痛点和收益。一旦完成这一步,接下来就很重要的是在利益相关者之间就需要解决的问题达成一致。如果大家一致认为数据对组织至关重要,那么数据战略的商业需求显而易见,随之而来的企业战略应当做出相应的更新。
5. 数据战略选择级联
一旦我们确立了公司需要将数据视为一种宝贵资产并加以管理、加以利用,就该设计数据战略了。
5.1 将选择级联转换为数据和人工智能
为了将 P2W 框架应用于数据战略设计,我们首先必须将企业上下文中的措辞转化为数据上下文。因此,我们首先定义一些核心元素,并在有帮助的情况下对其进行重新表述:
-
Offering → 数据产品:这包括与数据价值创造相关的数据产品、分析产品或服务。
-
客户 → 数据客户:接受数据产品服务的人。这些人最有可能是公司内部的利益相关者或团体,但也可以是外部用户。
-
公司 → 数据服务提供商:创建数据产品的人。
-
竞争:数据客户可能选择的替代方案,而非数据服务提供商。这可以从完全没有得到服务的数据客户、自己服务到使用外部服务的客户。
-
Geography → 重点领域:数据战略将专注于组织的哪些部门、团队、领域或区域。
-
渠道 → 数据交付渠道:数据客户如何获取数据产品。
-
生产阶段 → 数据生命周期管理:数据生命周期的哪些阶段是在内部完成或外包的。
此外,P2W 框架的策略定义可以直接扩展到数据战略:
数据战略是一个集成的选择集,独特地将企业定位于行业中,从而创造可持续的竞争优势和相对竞争对手的卓越价值,通过将数据作为资产加以利用。
5.2 数据战略选择级联
一旦核心元素被翻译,并且我们已经定义了数据战略对我们的意义,我们就可以最终定义数据战略选择级联:
-
胜利愿景:定义你的组织如何通过数据、分析和人工智能取得成功。
-
竞争领域:你将在其中选择是否与数据、分析和人工智能进行竞争的领域。这通常包括五个维度:i) 重点领域,ii) 数据客户,iii) 数据交付 渠道,iv) 数据产品,v) 数据生命周期管理。
-
如何取胜:你的数据战略将创造的战略优势。你将如何通过数据客户可持续取胜。这归结为降低成本或差异化。
-
必备能力:构建竞争优势所需的关键数据相关能力。
-
支持管理系统:支持和维持数据战略所需的基础设施(系统、流程、治理、指标和文化)。
图 6:数据战略选择级联的可视化
有关数据货币化选项的更多详细思考,作为数据战略的竞争领域的一部分,可以在[11]中找到。
5.3 数据战略选择级联备忘单
如果数据战略是关于做出选择,那么每个数据战略选择级联的元素的具体选择是什么?以下数据战略选择级联备忘单更详细地对比了企业战略和数据战略的级联,并提供了数据战略设计中需要做出的示例选择。
图 7:数据战略选择级联备忘单 — 级联如何转化为数据和人工智能
5.4 何时使用数据战略选择级联
类似于企业战略的级联,数据战略选择级联可以应用于多种情境,例如可以用来:
-
记录当前数据战略:这有助于在利益相关者中建立对现状的共同理解。通常在开始或重新启动数据战略设计过程时进行,如战略过程图中的阶段 1(识别问题)。
-
详细描述不同的可能性:作为数据战略设计过程的一部分(阶段 3:生成战略过程图的可能性)。通常,这里只需详细描述战略的核心,即竞争领域和胜利方法。
-
记录最终战略:在不同的数据战略可能性经过严格评估后,可以使用选择级联记录和传达最终战略。在实践中,这些文档通常会根据受众的具体需求,辅以更详细的文本和其他形式的沟通。
-
描述竞争对手的数据战略:数据战略选择级联可以用来大致勾画出竞争对手的数据战略,这在设计自己数据战略的过程中通常是一个不错的主意。
6. 两个示例数据战略
为了说明数据战略选择级联的应用,我们为公司 OŪRA 创造了两种(虚构的)数据战略可能性。回想一下,OŪRA 是一家健康科技公司,生产一款类似智能手表的戒指,能够捕捉身体数据。使用 P2W 框架为该公司制定的企业战略可见于[7]。
我们考虑了两种完全不同的可能性,旨在展示如何在数据战略设计过程中利用数据战略选择级联,统一记录、有效比较并沟通不同的战略可能性。
第一个战略可能性聚焦于为外部数据客户——最终用户——提供数据驱动的功能。
图 8:OŪRA 的第一种数据战略可能性:增强个性化与客户参与度
第二种可能性聚焦于为内部数据客户提供运营卓越的分析。
图 9:OŪRA 的第二种数据战略可能性:通过数据卓越提升运营效率与创新
请注意,组织和技术选择是在第五个元素——启用管理系统——中做出的,这些选择显然依赖于前面元素中做出的核心战略选择。选择级联可以防止用户在实际战略尚未定义时,就陷入仅仅选择技术解决方案的常见陷阱。
7. 结论与展望
我们已经看到,著名且经过实践验证的“Playing to Win”战略设计框架可以被修改为数据战略开发工具,进而形成数据战略选择级联。提供的备忘单可以指导数据战略设计团队快速应用该框架进行战略工作。因此,涉及数据战略设计时,不必重新发明轮子,你可以站在巨人的肩膀上,确保你的数据战略获得成功,使你的公司在数据领域获得胜利。
这就是全部吗?我们现在可以开始制定自己的数据战略了吗?
该级联仅仅是一个工具,用于表达我们在数据战略设计过程中创造的多种可能性。如果你仅仅使用这个级联写下一个你认为不错的数据战略可能性,你怎么知道这真的是你公司在数据方面获胜的最佳选择?
要弄清楚这一点,你需要应用战略流程图来设计你的数据战略,我将在我接下来的文章中详细介绍这一过程。
参考文献
1 Sebastian Wernicke, 《Data Inspired》(2024),由 Vahlen 出版社出版的德文书籍
2 卡罗琳·卡鲁瑟斯和彼得·杰克逊,《数据驱动的商业转型》(2019),Wiley 出版的书籍
3 延斯·林登,数据文化——什么与为什么? (2024),LinkedIn 动态
4 A.G. 拉夫利与罗杰·L·马丁,Playing to Win (2013),哈佛商业评论出版社出版的书籍
5 迈克尔·戈伊泰因,“Playing to Win”框架,第二部分——战略过程图,博客
[6] IDEO U,战略规划:如何开始,博客
[7] 迈克尔·戈伊泰因,“Playing to Win”框架——第三部分——战略选择级联,博客
[8] 罗杰·马丁,解码战略选择级联 (2023),Medium 文章
[9] 罗杰·马丁,计划不是战略 (2022),视频
[10] 延斯·林登,数据——数字化——技术:这三者都是一样的吗? (2024),LinkedIn 动态
[11] 延斯·林登,数据货币化——数据战略的“Where to Play” (2024),LinkedIn 动态
[12] 克里斯蒂娜·安东尼,超越流行词:制定有效的数据战略! (2024),LinkedIn 文章
[13] 德勤与谷歌新闻倡议,通过数据推动数字化转型:新闻与媒体公司如何利用数据创造价值 (2019),网站
[14] 马克·斯纽卡斯,战略到底是什么? (2024),LinkedIn 动态
[15] 罗杰·马丁,(Playing to Win) x 5 (2024),Medium 文章
[16] intrinsify 内容团队,现代经理如何区分:复杂 vs. 困难 (2023),网站
[17] 罗杰·马丁,Playing to Win/ 实践者见解 (2024),包含文章列表的网站
除非另有注明,所有图片均由作者提供。
附注:ChatGPT-4o 被用来补充备忘单示例选择中的某些元素,以及示例数据策略中的选择,以确保匿名性。