爬取 OpenAI 社区论坛数据全集
展示大规模数据收集实现,可作为训练集或应用数据来源,但需留意服务条款风险。
展示大规模数据收集实现,可作为训练集或应用数据来源,但需留意服务条款风险。
OpenAI 有一个官方开发者社区,由 Discourse 托管,这是寻求帮助和讨论 OpenAI API、ChatGPT、提示词等话题的中心场所。
论坛在 2021 年 3 月启动,至今已有 20,000 多用户发布了 100,000+ 篇帖子。
鉴于论坛的规模和话题集中度,这是一个了解开发者普遍情绪、识别用户面临的常见问题、获取 OpenAI 产品反馈的绝佳资源。
为了深入了解开发者体验和对特定产品的共识看法,我们下载了论坛常见类别中的所有帖子,具体如下:
API API/Bugs API/Deprecations API/Feedback
GPT Builders GPT Builders/Chat-Plugins GPT Builders/Plugin-Store
GPT Builders/Chat-Plugins
GPT Builders/Plugin-Store
我们创建了一个数据集,包含截至 2024 年 2 月 28 日为止,上述类别中的所有帖子和讨论。
我们相信,从人们的困难、开发者对使用 OpenAI 产品的体验情感中有很多东西可以学习。
这个数据集的创建是为了回答这些问题。从 OpenAI 的失败和成功中学习有很大潜力。
我们 Julep 团队很想听听你用这个数据集构建了什么!在 X/Twitter 上或通过电子邮件联系我们。
每个 Discourse 讨论在 URL 末尾添加 .json 都会返回 JSON 格式的数据。
讨论 URL:https://community.openai.com/t/{discussion_id}
讨论的 JSON 格式:https://community.openai.com/t/{discussion_id}.json
讨论的 Markdown 格式:https://community.openai.com/raw/{discussion_id}
原始数据通过自动化浏览器(使用 Playwright)爬取后汇总到单个 JSONL 文件中。
让我们一起看看数据集是如何制作的,然后展示一些我们注意到的初始趋势。
特性工程的简要演练。
由于每一行代表一个讨论,每个讨论包含多个线程中的帖子,数据集需要在帖子层级上进行规范化;这些是单个帖子的特性和 post_discussion 层级;这些是帖子所属讨论的特性。
帖子级特性:post_id;post_author
讨论级特性:post_discussion_id;post_category_id
%matplotlib widget
import pandas as pd
from datasets import Dataset, load_from_disk, load_dataset
hf_dataset = load_from_disk("9_dataset_with_topics")
# hf_dataset = load_dataset("julep-ai/openai-community-posts")
df = hf_dataset.to_pandas()
hf_dataset.features
{'post_discussion_id': Value(dtype='int64', id=None),
'post_discussion_tags': Sequence(feature=Value(dtype='string', id=None), length=-1, id=None),
'post_discussion_title': Value(dtype='string', id=None),
'post_discussion_created_at': Value(dtype='timestamp[ns, tz=UTC]', id=None),
'post_category_id': Value(dtype='int64', id=None),
'post_discussion_views': Value(dtype='int64', id=None),
'post_discussion_reply_count': Value(dtype='int64', id=None),
'post_discussion_like_count': Value(dtype='int64', id=None),
'post_discussion_participant_count': Value(dtype='int64', id=None),
'post_discussion_word_count': Value(dtype='float64', id=None),
'post_id': Value(dtype='int64', id=None),
'post_author': Value(dtype='string', id=None),
'post_created_at': Value(dtype='string', id=None),
'post_content': Value(dtype='string', id=None),
'post_read_count': Value(dtype='int64', id=None),
'post_reply_count': Value(dtype='int64', id=None),
'post_author_id': Value(dtype='int64', id=None),
'post_number': Value(dtype='int64', id=None),
'post_discussion_related_topics': Sequence(feature=Value(dtype='int64', id=None), length=-1, id=None),
'accepted_answer_post': Value(dtype='float64', id=None),
'post_content_raw': Value(dtype='string', id=None),
'post_category_name': Value(dtype='string', id=None),
'post_sentiment': Value(dtype='string', id=None),
'post_sentiment_score': Value(dtype='float64', id=None),
'post_content_cluster_embedding': Sequence(feature=Value(dtype='float64', id=None), length=-1, id=None),
'post_content_classification_embedding': Sequence(feature=Value(dtype='float64', id=None), length=-1, id=None),
'post_content_search_document_embedding': Sequence(feature=Value(dtype='float64', id=None), length=-1, id=None),
'tag1': Value(dtype='string', id=None),
'tag2': Value(dtype='string', id=None),
'tag3': Value(dtype='string', id=None),
'tag4': Value(dtype='string', id=None),
'post_discussion_url': Value(dtype='string', id=None),
'post_url': Value(dtype='string', id=None),
'topic_model_medium': Value(dtype='string', id=None),
'topic_model_broad': Value(dtype='string', id=None)}
# Total number of posts
print("Total number of posts: ", len(df))
# Total discussions
print("Total discussions: ", len(df["post_discussion_id"].unique()))
# Total number of users
print("Total number of users: ", len(df["post_author_id"].unique()))
Total number of posts: 97033
Total discussions: 18990
Total number of users: 21419
# Earliest and latest post
print("Earliest post: ", df["post_created_at"].min())
print("Latest post: ", df["post_created_at"].max())
Earliest post: 2021-03-10T20:39:25.848Z
Latest post: 2024-02-27T14:03:01.685Z
除了帖子和讨论级特性外,还计算了以下分类特性:
使用 Twitter-roBERTa-base 进行情感分析,我们为每个帖子生成了 post_sentiment 标签(负面、正面、中性)和 post_sentiment_score 置信度分数。
平均而言,大多数帖子是中性的。
df["post_sentiment"].value_counts(ascending=True, normalize=True)
post_sentiment
negative 0.185277
positive 0.219327
neutral 0.595395
Name: proportion, dtype: float64
但是,通过查看各类别的分布,我们发现 api 类别和 api/bugs 类别在不同类别中的负面情绪最多。
另一方面,社区和 gpts-builders/plugin-store 的正面情绪最多。
这是合理的,因为人们经常在社区中展示酷炫的项目、新闻和最新的 AI 发展!
# Group by 'post_category_name' and then apply normalized value_counts to 'post_sentiment'
sentiment_percentages = df.groupby("post_category_name")["post_sentiment"].apply(
lambda x: x.value_counts(normalize=True)
)
# Convert the Series to a DataFrame and reset the index
# sentiment_percentages = sentiment_percentages.mul(
# 100
# ) # Convert fractions to percentages
sentiment_percentages = sentiment_percentages.reset_index(name="percentage")
# Pivot the table for better readability
pivot_df = sentiment_percentages.pivot(
index="post_category_name", columns="level_1", values="percentage"
)
# Fill NaN values with zero if any sentiment labels are missing in a category
pivot_df = pivot_df.fillna(0)
pivot_df.reset_index()
pivot_df.columns.rename(None, inplace=True)
# Display the pivoted DataFrame in descending order
pivot_df
为了计算向量嵌入,我们在 text-embeddings-inference 的帮助下在本地运行了 Nomic Embed-Text v1.5。由于其 Matryoshka 可调整大小的特性,这些嵌入可用于许多未来的应用。
之所以选择 Nomic Embed v1.5,主要是因为其上下文长度很大。
import matplotlib.pyplot as plt
import seaborn as sns
df["post_content_raw_length"] = df["post_content_raw"].apply(len)
plt.figure(figsize=(12, 6))
sns.histplot(
df["post_content_raw_length"], bins=100, kde=False, cumulative=True, stat="density"
)
plt.title("CDF of Length Distribution of post_content_raw")
plt.xlabel("Length of post_content_raw")
plt.ylabel("Cumulative Density")
plt.show()
/home/glitch/.conda/envs/julep/lib/python3.10/site-packages/scipy/__init__.py:146: UserWarning: A NumPy version >=1.17.3 and <1.25.0 is required for this version of SciPy (detected version 1.26.4
warnings.warn(f"A NumPy version >={np_minversion} and <{np_maxversion}"
从累积分布频率图来看,99.7% 的帖子长度小于 8192 个字符。由于令牌数约为字符长度的 3/4,我们可以继续使用 truncate=True 对 post_content_raw 进行向量化,而不用担心大量知识和数据丢失。
Nomic 支持用于搜索、聚类和分类任务的嵌入。
我们已计算了 post_content_raw 字段上的所有三种嵌入类型。
df[
[
"post_content_cluster_embedding",
"post_content_classification_embedding",
"post_content_search_document_embedding",
]
]
97033 rows × 3 columns
Nomic 还提供 Atlas,一个数据映射工具,用于可视化、去重。它的话题建模特性和在地图上查看聚集数据集的能力是我们在这里使用它的原因。
from nomic import atlas, AtlasDataset
from IPython.core.display import HTML
dataset = AtlasDataset(identifier="glitch/openai-community-posts---clustering---v2")
2024-03-20 16:22:42.072 | INFO | nomic.dataset:__init__:779 - Loading existing dataset `glitch/openai-community-posts---clustering---v2``.
HTML(dataset.maps[0]._embed_html())
Atlas 是一个非常好用的工具,提供了强大的筛选功能集。欢迎探索上面的数据集!
作为一般规则,左侧的搜索、筛选、套索和精选工具有助于数据点的选择和细化。
右侧的查看设置提供了巧妙的可视化功能。
例如:筛选所有情感为负面的数据点,然后在查看设置中将"按以下方式着色"设置为对数级 post_discussion_views。
能够基于帖子 ID 执行相似度搜索是非常强大的功能。
允许使用这些嵌入向量在帖子内容上实现问答接口可以加快对社区帖子的研究(如果你知道应该问什么问题的话 :P)。
让我们查看一些与这篇关于函数调用的投诉帖子相似的帖子
map = dataset.maps[0]
neighbors, distances = map.embeddings.vector_search(ids=["Fjk"], k=7)
similar_datapoints = dataset.get_data(ids=neighbors[0])
for i, point in enumerate(similar_datapoints):
if i == 0:
print("Initial point:", point.get("post_discussion_title"), "\n")
print("Nearest neighbors:")
else:
print(point.get("post_discussion_title"))
Initial point: Gpt-4-1106-preview messes up function call parameters encoding
Nearest neighbors:
Gpt-4-1106-preview messes up function call parameters encoding
When structuring the output of function calls, there is Chinese character encoding issue resulting in garbled text
Confused on models that have function calling and when they get deprecated
Gpt-4-1106-preview is not generating utf-8
Gpt-3.5-turbo-1106 Calls multiple of the same function unecessarily
There is a mistake on the doc page of function calling
完成特征工程后,我们得到了 36 个可以探索的总特征。
这里,我们尝试提供一些有关数据集及其特征的基本信息,你可能想继续深入研究。
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import numpy as np
import seaborn as sns
print("Total features:", df.columns.__len__())
Total features: 36
og_df = df.copy(deep=True)
逻辑上讲,回复、点赞以及字数之间高度相关。
有趣的是,帖子中被接受的回答也会提升讨论的浏览量。
# Identify columns that contain lists, arrays or strings
cols_to_exclude = [
col
for col in df.columns
if df[col].apply(lambda x: isinstance(x, (list, np.ndarray, str))).any()
]
cols_to_exclude.extend(
[
"post_discussion_id",
"post_category_id",
"post_id",
"post_author_id",
]
)
# Create a new DataFrame that only includes columns with single numerical values
df_numerical = df.drop(columns=cols_to_exclude)
# Calculate the correlation matrix
corr = df_numerical.corr()
# Plot the heatmap
plt.figure(figsize=(15, 10))
sns.heatmap(corr, annot=False, cmap="coolwarm")
plt.xticks(rotation=45) # Rotate x-axis labels
plt.tight_layout() # Adjust plot margins
plt.show()
2023 年是 OpenAI 及其社区真正开始蓬勃发展的时期。2023 年 11 月的 OpenAI Dev Day 引发了人们对 OpenAI 的巨大关注。
有趣的是还可以看看每个月有多少用户加入了社区!
# Count sentiment labels by month
df["post_created_at"] = pd.to_datetime(df["post_created_at"])
df["year_month"] = df["post_created_at"].dt.to_period("M")
# Count sentiment labels by month
colors = {"negative": "red", "neutral": "blue", "positive": "green"}
sentiment_label_counts_by_month = (
df.groupby(["year_month", "post_sentiment"]).size().unstack(fill_value=0)
)
# Calculate proportions of sentiment labels by month
total_posts_per_month = sentiment_label_counts_by_month.sum(axis=1)
sentiment_label_proportions_by_month = sentiment_label_counts_by_month.divide(
total_posts_per_month, axis=0
)
sentiment_label_counts_by_month.plot(
kind="bar",
stacked=True,
figsize=(14, 8),
color=[colors[col] for col in sentiment_label_counts_by_month.columns],
)
plt.title("Volume of Posts Over Time")
plt.xlabel("Month")
plt.ylabel("Number of Posts")
plt.xticks(rotation=45)
plt.legend(title="Sentiment")
plt.tight_layout()
plt.show()
/tmp/ipykernel_186231/1065315333.py:3: UserWarning: Converting to PeriodArray/Index representation will drop timezone information.
df["year_month"] = df["post_created_at"].dt.to_period("M")
同样,在 Dev Day 之后,似乎有更多人表示满意!
df["post_created_at"] = pd.to_datetime(df["post_created_at"])
# Set the 'post_created_at' column as the index
df.set_index("post_created_at", inplace=True)
monthly_sentiment = (
df.resample("ME")["post_sentiment"].value_counts().unstack(fill_value=0)
)
# Plotting
plt.figure(figsize=(15, 8))
plt.plot(monthly_sentiment.index, monthly_sentiment.values)
for sentiment in monthly_sentiment.columns:
plt.plot(
monthly_sentiment.index, monthly_sentiment[sentiment], color=colors[sentiment]
)
# Formatting the x-axis to show Month-Year
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter("%b-%Y"))
plt.gca().xaxis.set_major_locator(mdates.MonthLocator())
# Improve x-axis labels readability
plt.gcf().autofmt_xdate()
plt.title("Average Sentiment Over Time")
plt.xlabel("Time")
plt.ylabel("Number of Posts")
plt.grid(True)
plt.show()
互动指标可以理解地在去年的两个主要事件周围达到峰值;GPT-4 的发布和 OpenAI Dev Day。
aggregated_data = df.resample("ME", on="post_discussion_created_at").agg(
{
"post_discussion_views": "sum",
"post_discussion_like_count": "sum",
"post_discussion_reply_count": "sum",
}
)
fig, ax1 = plt.subplots(figsize=(15, 7))
color = "tab:red"
ax1.set_xlabel("Time")
ax1.set_ylabel("Views", color=color)
ax1.plot(aggregated_data.index, aggregated_data["post_discussion_views"], color=color)
ax1.tick_params(axis="y", labelcolor=color)
ax2 = ax1.twinx() # instantiate a second axes that shares the same x-axis
color = "tab:blue"
ax2.set_ylabel(
"Likes and Replies", color=color
) # we already handled the x-label with ax1
ax2.plot(
aggregated_data.index,
aggregated_data["post_discussion_like_count"],
color="blue",
label="Likes",
)
ax2.plot(
aggregated_data.index,
aggregated_data["post_discussion_reply_count"],
color="green",
label="Replies",
)
ax2.tick_params(axis="y", labelcolor=color)
# Add a horizontal line and a label at November 2023
dev_day = mdates.date2num(
pd.to_datetime("2023-11-06")
) # Convert the date to matplotlib's internal format
gpt4_launch = mdates.date2num(pd.to_datetime("2023-03-14"))
ax2.axvline(dev_day, color="black", linestyle="--") # Add a vertical line
ax2.axvline(gpt4_launch, color="black", linestyle="--") # Add a vertical line
ax2.text(
dev_day,
ax2.get_ylim()[1],
"OpenAI Dev Day",
horizontalalignment="left",
verticalalignment="top",
) # Add a label
ax2.text(
gpt4_launch,
ax2.get_ylim()[1],
"GPT-4 Launch",
horizontalalignment="left",
verticalalignment="top",
) # Add a label
fig.tight_layout() # otherwise the right y-label is slightly clipped
plt.legend(loc="upper left")
plt.show()
df = og_df.copy(deep=True)
让我们为每个话题计算加权情感评分,并绘制其随时间的变化。
加权评分的范围如下:
-1.0 到 -0.1:负面
df["post_created_at"] = pd.to_datetime(df["post_created_at"])
# Assign numeric values to sentiment labels.
sentiment_numeric = {"negative": -1, "neutral": 0, "positive": 1}
df["sentiment_numeric"] = df["post_sentiment"].map(sentiment_numeric)
# Calculate weighted sentiment score.
df["weighted_sentiment"] = df["sentiment_numeric"] * df["post_sentiment_score"]
# Group by topic and month, then calculate the average weighted sentiment.
df["year_month"] = df["post_created_at"].dt.to_period("M")
grouped = (
df.groupby(["topic_model_broad", "year_month"])["weighted_sentiment"]
.mean()
.reset_index()
)
# Pivot for easier plotting.
pivot_table = grouped.pivot(
index="year_month", columns="topic_model_broad", values="weighted_sentiment"
)
df["adjusted_weight"] = df.apply(
lambda row: (
row["post_sentiment_score"] * 1.5
if row["sentiment_numeric"] != 0
else row["post_sentiment_score"]
),
axis=1,
)
# 使用调整后的权重计算加权情感分数
df["weighted_sentiment_adjusted"] = df["sentiment_numeric"] * df["adjusted_weight"]
# 按主题和月份分组,计算平均调整加权情感
df["year_month"] = df["post_created_at"].dt.to_period("M")
grouped_adjusted = (
df.groupby(["topic_model_broad", "year_month"])["weighted_sentiment_adjusted"]
.mean()
.reset_index()
)
# 透视以便于绘图
pivot_table_adjusted = grouped_adjusted.pivot(
index="year_month",
columns="topic_model_broad",
values="weighted_sentiment_adjusted",
)
# 绘图
df["post_created_at"] = pd.to_datetime(df["post_created_at"])
# 为情感标签分配数值
sentiment_numeric = {"negative": -1, "neutral": 0, "positive": 1}
df["sentiment_numeric"] = df["post_sentiment"].map(sentiment_numeric)
# 计算加权情感分数
df["weighted_sentiment"] = df["sentiment_numeric"] * df["post_sentiment_score"]
# 按主题和月份分组,计算平均加权情感
df["year_month"] = df["post_created_at"].dt.to_period("M")
grouped = (
df.groupby(["topic_model_broad", "year_month"])["weighted_sentiment"]
.mean()
.reset_index()
)
# 透视以便于绘图
pivot_table = grouped.pivot(
index="year_month", columns="topic_model_broad", values="weighted_sentiment"
)
df["adjusted_weight"] = df.apply(
lambda row: (
row["post_sentiment_score"] * 1.5
if row["sentiment_numeric"] != 0
else row["post_sentiment_score"]
),
axis=1,
)
# 使用调整后的权重计算加权情感分数
df["weighted_sentiment_adjusted"] = df["sentiment_numeric"] * df["adjusted_weight"]
# 按主题和月份分组,计算平均调整加权情感
df["year_month"] = df["post_created_at"].dt.to_period("M")
grouped_adjusted = (
df.groupby(["topic_model_broad", "year_month"])["weighted_sentiment_adjusted"]
.mean()
.reset_index()
)
# 透视以便于绘图
pivot_table_adjusted = grouped_adjusted.pivot(
index="year_month",
columns="topic_model_broad",
values="weighted_sentiment_adjusted",
)
# 绘图
plt.figure(figsize=(14, 8))
pivot_table_adjusted.index = pivot_table_adjusted.index.to_timestamp()
pivot_table_adjusted.drop(["Emoji (8)"], axis=1, inplace=True)
for column in pivot_table_adjusted.columns:
clean_series_adjusted = pivot_table_adjusted[column].dropna()
plt.plot(
clean_series_adjusted.index,
clean_series_adjusted,
marker="",
linewidth=2,
label=column,
)
# 添加水平色带
plt.fill_between(clean_series_adjusted.index, -0.1, 0.1, color="blue", alpha=0.1)
plt.fill_between(clean_series_adjusted.index, 0.1, 1.0, color="green", alpha=0.1)
plt.fill_between(clean_series_adjusted.index, -1.0, -0.1, color="red", alpha=0.1)
plt.title("按主题模型的随时间变化的平均调整加权情感分数")
plt.xlabel("时间")
plt.ylabel("平均调整加权情感分数")
/tmp/ipykernel_186231/2422921945.py:11: UserWarning: Converting to PeriodArray/Index representation will drop timezone information.
df["year_month"] = df["post_created_at"].dt.to_period("M")
/tmp/ipykernel_186231/2422921945.py:37: UserWarning: Converting to PeriodArray/Index representation will drop timezone information.
df["year_month"] = df["post_created_at"].dt.to_period("M")
/tmp/ipykernel_186231/2422921945.py:62: UserWarning: Converting to PeriodArray/Index representation will drop timezone information.
df["year_month"] = df["post_created_at"].dt.to_period("M")
/tmp/ipykernel_186231/2422921945.py:88: UserWarning: Converting to PeriodArray/Index representation will drop timezone information.
df["year_month"] = df["post_created_at"].dt.to_period("M")
# 主题模型的长度
print("中等主题模型长度:", len(df["topic_model_medium"].unique()))
print("广泛主题模型长度:", len(df["topic_model_broad"].unique()))
中等主题模型长度:256
广泛主题模型长度:8
现在换个角度,通过将帖子按主题模型关键词进行过滤,来观察常见的投诉和反馈。在经过主题模型的 256 个主题后,我们筛选了以下 48 个主题。
这些主题大致涵盖了所有关于使用 OpenAI 产品(如 GPT、Assistants API、Embeddings)的开发讨论以及围绕这些产品的常见问题、常见问题解答和投诉。
GPUs、性能、计算
Assistants、API、平台
无效请求错误
selected_topics = [
"Assistant",
"API Usage",
"Chatbot",
"Python3 Packages",
"API Development",
"Performance",
"GPUs, Performance, Compute",
"Pricing",
"Retrying",
"Product Development",
"Threads",
"Embeddings",
"Parallel Methods",
"Schema",
"Knowledge Retrieval",
"OpenAI",
"Assistants, API, Platform",
"JSON Format",
"JSON Format (2)",
"Embeddings (2)",
"APIs",
"Error Handling",
"Embeddings (3)",
"Streaming",
"Invalid Request Error",
"Completion",
"AI Assistants",
"JSON Validation",
"Conversation History",
"Assistant Tools",
"Functions",
"Functions (2)",
"Functions (3)",
"Functions (4)",
"Threads (2)",
"User Assistant",
"API",
"Vector Space Search",
"Summarization",
"Embeddings (5)",
"Logit",
"Embedding Vectors",
"GPTs",
"Functions",
"Embedding",
"API Development",
"AI Development",
"Assistants",
]
topic_df = df[df["topic_model_medium"].isin(selected_topics)].copy(deep=True)
selected_topics.__len__()
48
通过在这些主题上有选择性地进行分析,我们可以深入研究个别问题,并直观地了解开发者可能存在的负面情绪趋势。
# 查看主题模型在选定主题中、帖子情感为负面且按帖子讨论浏览量排序的行
topic_df[
(topic_df["post_sentiment"] == "negative")
]