注意:为了根据文本文件回答问题,我们推荐使用“使用嵌入进行问答”中的过程。下面的某些代码可能依赖于已弃用的 API 端点。
2. 创建合成问答数据集
我们使用davinci-instruct-beta-v3
,一个专门用于遵循指令的模型,根据给定的上下文创建问题。然后,我们还使用davinci-instruct-beta-v3
在给定相同上下文的情况下回答这些问题。
这很昂贵,而且耗时很长,因为我们需要为每个部分调用 davinci 引擎。您可以直接下载最终数据集。
我们正在使用通过上一个笔记本创建的数据集
2.1 读取数据并创建上下文
通过连接标题、标题和该部分的内容来创建上下文
import pandas as pd
df = pd.read_csv('olympics-data/olympics_sections.csv')
df['context'] = df.title + "\n" + df.heading + "\n\n" + df.content
df.head()
title | heading | content | tokens | context | |
---|---|---|---|---|---|
0 | 2020年夏季奥运会 | 摘要 | 2020年夏季奥运会(日语:2020年夏季オリン... | 713 | 2020年夏季奥运会 摘要 2020年夏季奥运会(日语:2020年夏季オリン... |
1 | 2020年夏季奥运会 | 主办城市选择 | 国际奥委会(IOC)投票... | 126 | 2020年夏季奥运会 主办城市选择 国际奥委会(IOC)投票... |
2 | 2020年夏季奥运会 | COVID-19大流行的影响 | 2020年1月,人们对... | 369 | 2020年夏季奥运会 COVID-19大流行的影响 2020年1月,人们对... |
3 | 2020年夏季奥运会 | 资格赛取消和推迟 | 对疫情的担忧开始影响资格赛... | 298 | 2020年夏季奥运会 资格赛取消和推迟 对疫情的担忧开始影响资格赛... |
4 | 2020年夏季奥运会 | 对兴奋剂检测的影响 | 强制兴奋剂检测受到了严重限制... | 163 | 2020年夏季奥运会 对兴奋剂检测的影响 强制兴奋剂检测受到了严重限制... |
2.2 根据上下文创建问题
使用 davinci-instruct 生成与维基百科章节内容相关的若干合理问题。
注意:我们使用了 temperature=0,但尝试使用更高的 temperature 以获得更多样化的问题可能会有益。
警告:此步骤将耗时很长,并消耗大量 token,因为它会为每个部分调用 davinci-instruct 来生成若干问题。
from openai import OpenAI
client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY", "<your OpenAI API key if not set as env var>"))
def get_questions(context):
try:
response = client.chat.completions.create(model="davinci-instruct-beta-v3",
prompt=f"Write questions based on the text below\n\nText: {context}\n\nQuestions:\n1.",
temperature=0,
max_tokens=257,
top_p=1,
frequency_penalty=0,
presence_penalty=0,
stop=["\n\n"])
return response.choices[0].text
except:
return ""
df['questions']= df.context.apply(get_questions)
df['questions'] = "1." + df.questions
print(df[['questions']].values[0][0])
1. 2020年夏季奥运会是什么?
2. 2020年夏季奥运会何时举行?
3. 谁在2020年夏季奥运会上获得奖牌最多?
4. 谁在2020年夏季奥运会上获得金牌最多?
5. 谁在2020年夏季奥运会上获得奖牌最多?
该提示旨在生成若干问题。上面示例问题是根据 2020 年夏季奥运会页面的摘要部分生成的。
我们可以看到问题 3 和 5 是重复的。有时生成的问题在没有上下文的情况下可能会含糊不清。我们将表明,尽管存在这些限制,我们仍然可以创建一个成功的模型。
print(df.content.values[0])
2020年夏季奥运会(日语:2020年夏季オリンピック, Hepburn: Nisen Nijū-nen Kaki Orinpikku),正式名称为第32届奥林匹克运动会(第三十二回オリンピック競技大会, Dai Sanjūni-kai Orinpikku Kyōgi Taikai),品牌名称为东京2020(東京2020, Tōkyō Nii Zero Nii Zero),是一项国际综合性体育赛事,于2021年7月23日至8月8日在日本东京举行,部分预赛已于7月21日开始。
2013年9月7日,国际奥委会(IOC)在日本东京举行的第125届全会上选择了东京作为主办城市。该赛事原定于2020年7月24日至8月9日举行,但由于COVID-19大流行,于2020年3月推迟至2021年,这是奥运会历史上首次出现这种情况(之前的奥运会曾被取消但未重新安排)。然而,出于营销和品牌推广的目的,该赛事保留了“东京2020”的名称。由于宣布了针对大东京地区的紧急状态以应对疫情,大部分比赛都在没有公众观众的情况下进行。残奥会于2021年8月24日至9月5日举行,比奥运会结束晚了16天。2020年奥运会是日本第四次举办奥运会,此前分别在1964年东京(夏季)、1972年札幌(冬季)和1998年长野(冬季)举办过奥运会。东京是亚洲第二个举办夏季奥运会的城市。2020年奥运会是东亚连续举办的第二届奥运会,此前是2018年韩国平昌冬季奥运会,之后是2022年中国北京冬季奥运会。
2020年新增了现有体育项目的比赛,包括3x3篮球、自由式小轮车以及多个现有体育项目的混合性别团体比赛,以及男子麦迪逊自行车赛的回归和女子同项比赛的引入。新的IOC政策也允许主办组织委员会为仅一届奥运会增设新的体育项目。日本奥委会增设的项目包括棒球和垒球、空手道、运动攀岩、冲浪和滑板,其中后四项首次亮相奥运会,后三项将继续保留在奥运会赛程中。美国在奖牌总数和金牌总数上均位居榜首(39枚金牌,113枚奖牌),中国以38枚金牌和88枚奖牌位居第二。东道主日本获得第三名,创造了其代表团在奥运会上的金牌和奖牌总数纪录,分别为27枚金牌和58枚奖牌。英国以22枚金牌和65枚奖牌位居第四,成为夏季奥运会历史上第一个在主办奥运会后的两届奥运会中奖牌总数增加或持平的国家。代表俄罗斯参赛的代表团(ROC)(不要与中华民国(台湾)混淆,中华民国以中华台北的名义参赛,而不是ROC)以20枚金牌位居第五,奖牌总数位居第三,共71枚奖牌。百慕大、菲律宾和卡塔尔赢得了他们有史以来第一枚奥运金牌。布基纳法索、圣马力诺和土库曼斯坦赢得了他们有史以来第一枚奥运奖牌。
2.3 根据上下文创建答案
使用 davinci-instruct 根据相关的维基百科章节内容回答问题
注意:我们使用了 temperature=0,但尝试使用更高的 temperature 以获得更多样化的问题可能会有益。
警告:此步骤将耗时很长,并消耗大量 token,因为它会为每个部分调用 davinci-instruct 来回答所有问题。
def get_answers(row):
try:
response = client.chat.completions.create(
engine="davinci-instruct-beta-v3",
prompt=f"Write answer based on the text below\n\nText: {row.context}\n\nQuestions:\n{row.questions}\n\nAnswers:\n1.",
temperature=0,
max_tokens=257,
top_p=1,
frequency_penalty=0,
presence_penalty=0
)
return response.choices[0].text
except Exception as e:
print (e)
return ""
df['answers']= df.apply(get_answers, axis=1)
df['answers'] = "1." + df.answers
df = df.dropna().reset_index().drop('index',axis=1)
print(df[['answers']].values[0][0])
1. 2020年夏季奥运会是一项于2021年7月23日至8月8日在日本东京举行的国际综合性体育赛事。
2. 2020年夏季奥运会于2021年7月23日至8月8日举行。
3. 美国在奖牌总数和金牌总数上均位居榜首(39枚金牌,113枚奖牌),中国以38枚金牌和88枚奖牌位居第二。
4. 美国在奖牌总数和金牌总数上均位居榜首(39枚金牌,113枚奖牌),中国以38枚金牌和88枚奖牌位居第二。
5. 美国在奖牌总数和金牌总数上均位居榜首(39枚金牌,113枚奖牌),中国以38枚金牌和88枚奖牌位居第二。
这些是根据主办城市选择的上下文对上述问题的回答。
我们可以看到,答案 3-5 包含正确答案,但答案不是直接回答问题,而是逐字提取。尽管偶尔会出现质量较低的答案,但我们将表明,鉴于大量的示例,该模型可以很好地学习任务。
2.4 保存基于维基百科章节的奥运会问答数据集
我们将文件保存供下一个笔记本使用
df.to_csv('olympics-data/olympics_qa.csv', index=False)
2.5 搜索文件(已弃用)
我们创建一个搜索文件(API 参考),可用于在提问时检索相关上下文。
已弃用:/search 端点已弃用,推荐使用嵌入。嵌入更便宜、更快,并且可以支持更好的搜索体验。请参阅问答指南了解使用嵌入的搜索实现
df = df[df.tokens<2000]
df[['context', 'tokens']].rename(columns={'context':'text','tokens':'metadata'}).to_json('olympics-data/olympics_search.jsonl', orient='records', lines=True)
search_file = client.files.create(
file=open("olympics-data/olympics_search.jsonl"),
purpose='search'
)
olympics_search_fileid = search_file['id']
2.6 根据提供的上下文回答问题
我们将使用答案端点的简单实现。这通过简单地使用/search 端点来工作,该端点在索引文件上进行搜索以获取可以包含在上下文中的相关部分,然后是给定指定模型的问题和回答提示。
from answers_with_ft import create_context, answer_question
print(create_context("Where did women's 4 x 100 metres relay event take place during the 2020 Summer Olympics?", olympics_search_fileid, max_len=400))
Athletics at the 2020 Summer Olympics – Women's 4 × 100 metres relay
Summary
The women's 4 × 100 metres relay event at the 2020 Summer Olympics took place on 5 and 6 August 2021 at the Japan National Stadium. There were 16 competing relay teams, with each team having 5 members from which 4 were selected in each round.
###
Athletics at the 2020 Summer Olympics – Men's 4 × 100 metres relay
Qualification
National Olympic Committees (NOCs) could qualify one relay team in one of three following ways:
The top 8 NOCs at the 2019 World Athletics Championships qualified a relay team.
The top 8 NOCs at the 2019 World Athletics Relays qualified a relay team.
Where an NOC placed in the top 8 at both the 2019 World Championships and the 2021 World Relays, the quota place was allocated to the world top list as of 29 June 2021. In this case, 4 teams did so, so there are 4 places available through the world rankings.A total of five athletes may be entered for a relay team. Should a NOC have also entered individual athletes in the corresponding individual event (100 m), the entered individual athletes must be included in the total of five (5) athletes entered for the relay event. In addition of five, NOCs can nominate a maximum of one alternate athlete for each team.
The qualifying period was originally from 1 May 2019 to 29 June 2020. Due to the COVID-19 pandemic, the period was suspended from 6 April 2020 to 30 November 2020, with the end date extended to 29 June 2021. The qualifying time standards could be obtained in various meets during the given period that have the approval of the IAAF. Both indoor and outdoor meets are eligible. The most recent Area Championships may be counted in the ranking, even if not during the qualifying period.
answer_question(olympics_search_fileid, "davinci-instruct-beta-v3",
"Where did women's 4 x 100 metres relay event take place during the 2020 Summer Olympics?")
' 日本国家体育场'
在对模型进行问答微调后,我们将能够使用它来代替davinci-instruct-beta-v3
,以便在无法根据上下文回答问题时获得更好的答案。我们看到了davinci-instruct-beta-v3
的一个缺点,它总是尝试回答问题,无论相关上下文是否存在。(请注意,第二个问题是关于一个设定在 2024 年的未来事件。)
answer_question(olympics_search_fileid, "davinci-instruct-beta-v3",
"Where did women's 4 x 100 metres relay event take place during the 2048 Summer Olympics?", max_len=1000)
' 日本国家体育场'
我们可以看到,即使问题无法根据提供的上下文回答,davinci 也有回答问题的倾向。请注意关于 2048 年夏季奥运会的问题,该奥运会尚未举行,而检索到的内容仅返回了 2020 年的结果。
2.7 (可选)调查搜索端点返回相关上下文的可能性
def check_context(title, heading, question, max_len=1800, search_model='ada', max_rerank=10):
"""
评估搜索模型检索正确上下文的性能
参数
----------
title: str
维基百科页面的标题
heading: str
维基百科章节的标题
qusetion: str
问题
max_len: int
上下文的最大长度
search_model: str
要使用的搜索模型 - `ada` 最具成本效益
max_rerank: int
要用于搜索模型的最多重新排序文档数
返回
-------
rank: int
正确上下文的排名
token_length: int
检索正确上下文所需的 token 数
"""
try:
# TODO: openai.Engine(search_model) 已弃用
results = openai.Engine(search_model).search(
search_model=search_model,
query=question,
max_rerank=max_rerank,
file=olympics_search_fileid,
return_metadata=True
)
index=-1
returns = []
cur_len = 0
for result in results['data']:
cur_len += int(result['metadata']) + 4 # 我们添加 4 个 token 用于分隔符 `\n\n###\n\n`
if cur_len > max_len:
break
returns.append(result['text'])
res = result['text'].split('\n')
if res[0] == title and res[1] == heading:
index = len(returns) - 1
break
return index, cur_len
except Exception as e:
#print (e)
return []
print(check_context("Athletics at the 2020 Summer Olympics – Women's 4 × 100 metres relay", "Summary", "Where did women's 4 x 100 metres relay event take place during the 2020 Summer Olympics?", max_len=10000))
(0, 58)
我们利用根据上下文生成的问题来估计检索原始上下文的频率。这些问题很嘈杂,因此这不是一个完美的估计。
我们的问题和答案以编号的项目符号开头,但由于生成方式,它们缺少第一个数字,因此我们将“1.”添加到问题(和答案)列表中。
我们计算使用 ada 搜索检索到的部分的排名以及检索完整相关部分所需的上下文 token 数。
ada_results = df.apply(lambda x: [
check_context( x.title,
x.heading,
q[3:], # remove the number prefix
max_len=1000000, # set a large number to get the full context
search_model='ada',
max_rerank=200,
)
for q in (x.questions).split('\n') # split the questions
if len(q) >10 # remove the empty questions
], axis=1)
ada_results.head()
0 [(132, 27104), (-1, 22939), (8, 2151), (2, 121...
1 [(4, 1737), (0, 130), (8, 744), (96, 17208), (...
2 [(0, 373), (0, 373), (-1, 40610), (1, 570)]
3 [(0, 302), (0, 302), (5, 968), (8, 1425)]
4 [(0, 167), (0, 167), (2, 1442)]
Name: ada, dtype: object
out = pd.concat([ada_results], axis=1)
out.columns = ['ada']
out.to_csv('olympics-data/search_engine_results.csv')
def expand_lists(out):
"""
将包含列表的 pandas Series 展开为 Series,其中每个列表元素本身成为一个值
输入是每段的行,其中包含多个问题
输出是每个问题的行
"""
cols = [pd.DataFrame(out[name].tolist()).stack().reset_index(level=1, drop=True).rename(name) for name in out.columns]
return pd.concat(cols, axis=1)
out_expanded = expand_lists(out)
out_expanded['rank'] = out_expanded.ada.apply(lambda x: x[0] if x != [] else -2)
out_expanded['tokens'] = out_expanded.ada.apply(lambda x: x[1] if x != [] else -2)
within_2k = (out_expanded.tokens < 2000).mean()
print(f"{within_2k*100:.1f}% of relevant paragraphs are retrieved within the first 2k tokens")
74.3% 的相关段落可在前 2k 个 token 内检索到
在此数据集中,74% 的时间可以获得相关上下文
outside_200 = (out_expanded['rank'] == -1).mean()
print(f"{outside_200*100:.1f}% of relevant paragraphs are not retrieved within the first 200 results")
7.4% 的相关段落未在前 200 个结果中检索到
7.4% 的时间,这是因为搜索算法的关键字搜索部分在前 200 个结果中未能检索到相关上下文。 18.3% 的时间,这是因为语义搜索未能将相关上下文放在前 2000 个 token 中。
import matplotlib.pyplot as plt
# plot a histogram, and add axis descriptions and title
out_expanded[(out_expanded['rank'] >=0)&(out_expanded['rank'] <30)]['rank'].hist(bins=29)
plt.xlabel('rank')
plt.ylabel('count')
plt.title('Histogram of ranks of retrieved paragraphs')
plt.show()
out_expanded[(out_expanded.tokens>=0)&(out_expanded.tokens < 2000)]['tokens'].hist(bins=29)
plt.xlabel('tokens')
plt.ylabel('count')
plt.title('Histogram of the number of minimum tokens needed')
plt.show()
我们可以看到,上下文最有可能作为前几个结果之一返回,并且最有可能在最初的 200-500 个 token 内返回。
# normalized value_counts
out_expanded['rank'].value_counts(normalize=True).sort_index()[:13]
rank | |
---|---|
-2 | 0.000063 |
-1 | 0.074428 |
0 | 0.453420 |
1 | 0.089515 |
2 | 0.047146 |
3 | 0.032437 |
4 | 0.024139 |
5 | 0.019676 |
6 | 0.015967 |
7 | 0.013452 |
8 | 0.011189 |
9 | 0.009869 |
10 | 0.009178 |