RankLLM 重排序器演示 (梵高维基)¶
本演示展示了如何使用 RankLLM 对段落进行重排序。
RankLLM 提供了一系列列表式重排序器,其重点在于针对该任务进行微调的开源 LLM,例如 RankVicuna 和 RankZephyr。
它比较了对梵高维基百科的查询搜索结果,包括仅使用检索(来自 llama-index 的 VectorIndexRetriever)和使用 RankLLM 进行检索+重排序。我们展示了使用 RankZephyr 重排序器对 50 个候选结果进行重排序的示例,该重排序器使用列表式滑动窗口算法。
依赖
- CUDA
- 内置检索器使用了 Pyserini,需要
JDK11
、PyTorch
和Faiss
castorini/rank_llm¶
基于 LLM 的重排序模型套件(例如,RankZephyr
、LiT5
、RankVicuna
) 网站:http://rankllm.ai\
输入 [ ]
已复制!
%pip install llama-index-core
%pip install llama-index-llms-openai
%pip install llama-index-postprocessor-rankllm-rerank
%pip install rank-llm
%pip install llama-index-core %pip install llama-index-llms-openai %pip install llama-index-postprocessor-rankllm-rerank %pip install rank-llm
输入 [ ]
已复制!
import nest_asyncio
nest_asyncio.apply()
import nest_asyncio nest_asyncio.apply()
输入 [ ]
已复制!
import logging
import sys
logging.basicConfig(stream=sys.stdout, level=logging.INFO)
logging.getLogger().addHandler(logging.StreamHandler(stream=sys.stdout))
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
from llama_index.core.postprocessor import LLMRerank
from llama_index.llms.openai import OpenAI
from IPython.display import Markdown, display
import logging import sys logging.basicConfig(stream=sys.stdout, level=logging.INFO) logging.getLogger().addHandler(logging.StreamHandler(stream=sys.stdout)) from llama_index.core import VectorStoreIndex, SimpleDirectoryReader from llama_index.core.postprocessor import LLMRerank from llama_index.llms.openai import OpenAI from IPython.display import Markdown, display
输入 [ ]
已复制!
import os
OPENAI_API_KEY = "sk-"
os.environ["OPENAI_API_KEY"] = OPENAI_API_KEY
import os OPENAI_API_KEY = "sk-" os.environ["OPENAI_API_KEY"] = OPENAI_API_KEY
输入 [ ]
已复制!
from llama_index.core import Settings
Settings.llm = OpenAI(temperature=0, model="gpt-3.5-turbo")
Settings.chunk_size = 512
from llama_index.core import Settings Settings.llm = OpenAI(temperature=0, model="gpt-3.5-turbo") Settings.chunk_size = 512
加载数据,构建索引¶
输入 [ ]
已复制!
from pathlib import Path
import requests
wiki_titles = [
"Vincent van Gogh",
]
data_path = Path("data_wiki")
for title in wiki_titles:
response = requests.get(
"https://en.wikipedia.org/w/api.php",
params={
"action": "query",
"format": "json",
"titles": title,
"prop": "extracts",
"explaintext": True,
},
).json()
page = next(iter(response["query"]["pages"].values()))
wiki_text = page["extract"]
if not data_path.exists():
Path.mkdir(data_path)
with open(data_path / f"{title}.txt", "w") as fp:
fp.write(wiki_text)
from pathlib import Path import requests wiki_titles = [ "Vincent van Gogh", ] data_path = Path("data_wiki") for title in wiki_titles: response = requests.get( "https://en.wikipedia.org/w/api.php", params={ "action": "query", "format": "json", "titles": title, "prop": "extracts", "explaintext": True, }, ).json() page = next(iter(response["query"]["pages"].values())) wiki_text = page["extract"] if not data_path.exists(): Path.mkdir(data_path) with open(data_path / f"{title}.txt", "w") as fp: fp.write(wiki_text)
输入 [ ]
已复制!
# load documents
documents = SimpleDirectoryReader("./data_wiki/").load_data()
# load documents documents = SimpleDirectoryReader("./data_wiki/").load_data()
输入 [ ]
已复制!
index = VectorStoreIndex.from_documents(
documents,
)
index = VectorStoreIndex.from_documents( documents, )
INFO:httpx:HTTP Request: POST https://api.openai.com/v1/embeddings "HTTP/1.1 200 OK" HTTP Request: POST https://api.openai.com/v1/embeddings "HTTP/1.1 200 OK"
检索 + RankLLM 重排序 (滑动窗口)¶
- 设置检索器和重排序器
- 在不进行重排序的情况下检索给定搜索查询的结果
- 使用 RankZephyr 重排序检索给定搜索查询的结果
输入 [ ]
已复制!
from llama_index.core.retrievers import VectorIndexRetriever
from llama_index.core import QueryBundle
from llama_index.postprocessor.rankllm_rerank import RankLLMRerank
import pandas as pd
import torch
from IPython.display import display, HTML
def get_retrieved_nodes(
query_str,
vector_top_k=10,
reranker_top_n=3,
with_reranker=False,
model="rank_zephyr",
window_size=None,
):
query_bundle = QueryBundle(query_str)
# configure retriever
retriever = VectorIndexRetriever(
index=index,
similarity_top_k=vector_top_k,
)
retrieved_nodes = retriever.retrieve(query_bundle)
retrieved_nodes.reverse()
if with_reranker:
# configure reranker
reranker = RankLLMRerank(
model=model, top_n=reranker_top_n, window_size=window_size
)
retrieved_nodes = reranker.postprocess_nodes(
retrieved_nodes, query_bundle
)
# clear cache, rank_zephyr uses 16GB of GPU VRAM
del reranker
torch.cuda.empty_cache()
return retrieved_nodes
def pretty_print(df):
return display(HTML(df.to_html().replace("\\n", "<br>")))
def visualize_retrieved_nodes(nodes) -> None:
result_dicts = []
for node in nodes:
result_dict = {"Score": node.score, "Text": node.node.get_text()}
result_dicts.append(result_dict)
pretty_print(pd.DataFrame(result_dicts))
from llama_index.core.retrievers import VectorIndexRetriever from llama_index.core import QueryBundle from llama_index.postprocessor.rankllm_rerank import RankLLMRerank import pandas as pd import torch from IPython.display import display, HTML def get_retrieved_nodes( query_str, vector_top_k=10, reranker_top_n=3, with_reranker=False, model="rank_zephyr", window_size=None, ): query_bundle = QueryBundle(query_str) # configure retriever retriever = VectorIndexRetriever( index=index, similarity_top_k=vector_top_k, ) retrieved_nodes = retriever.retrieve(query_bundle) retrieved_nodes.reverse() if with_reranker: # configure reranker reranker = RankLLMRerank( model=model, top_n=reranker_top_n, window_size=window_size ) retrieved_nodes = reranker.postprocess_nodes( retrieved_nodes, query_bundle ) # clear cache, rank_zephyr uses 16GB of GPU VRAM del reranker torch.cuda.empty_cache() return retrieved_nodes def pretty_print(df): return display(HTML(df.to_html().replace("\\n", "
"))) def visualize_retrieved_nodes(nodes) -> None: result_dicts = [] for node in nodes: result_dict = {"Score": node.score, "Text": node.node.get_text()} result_dicts.append(result_dict) pretty_print(pd.DataFrame(result_dicts))
"))) def visualize_retrieved_nodes(nodes) -> None: result_dicts = [] for node in nodes: result_dict = {"Score": node.score, "Text": node.node.get_text()} result_dicts.append(result_dict) pretty_print(pd.DataFrame(result_dicts))
输入 [ ]
已复制!
new_nodes = get_retrieved_nodes(
"Which date did Paul Gauguin arrive in Arles?",
vector_top_k=50,
with_reranker=False,
)
visualize_retrieved_nodes(new_nodes[:3])
new_nodes = get_retrieved_nodes( "Which date did Paul Gauguin arrive in Arles?", vector_top_k=50, with_reranker=False, ) visualize_retrieved_nodes(new_nodes[:3])
INFO:httpx:HTTP Request: POST https://api.openai.com/v1/embeddings "HTTP/1.1 200 OK" HTTP Request: POST https://api.openai.com/v1/embeddings "HTTP/1.1 200 OK"
得分 | 文本 | |
---|---|---|
0 | 0.766322 | 黄色对他而言意义最为重大,因为它象征着情感的真实。他用黄色象征阳光、生命和上帝。 梵高努力成为一个描绘乡村生活和自然的画家;在阿尔勒的第一个夏天,他使用了新的调色板来描绘风景和传统的乡村生活。他相信自然背后存在着一种力量,这促使他试图捕捉那种力量感,或艺术中自然的本质,有时通过使用符号。他的播种者描绘,起初模仿了让-弗朗索瓦·米勒,反映了托马斯·卡莱尔和弗里德里希·尼采关于体力劳动英雄主义的思想,以及梵高的宗教信仰:播种者如同基督在炽热的阳光下播撒生命。这些是他经常回归并重新创作和发展的主题和图案。他的花卉画充满了象征意义,但与传统的基督教象征不同,他创造了自己的象征,生命在阳光下度过,劳动是生命的寓言。在阿尔勒,在描绘了春季花朵并学会捕捉明亮的阳光后,他获得了信心,准备绘制《播种者》。 梵高停留在他所谓的“现实的外衣”之内,并批判过于程式化的作品。他后来写道,《星夜》的抽象程度走得太远,现实“退得太远”。休斯将其描述为一个极致的视觉狂喜时刻:星星在巨大的漩涡中,让人想起葛饰北斋的《神奈川冲浪里》,天空中的运动反映了下方柏树的运动,画家的视野被“转化为一种厚重、强调的颜料浆”。 在 1885 年到 1890 年去世之间,梵高似乎一直在构建一部作品集,反映了他的个人愿景并可能获得商业成功。他受到布兰克关于风格定义的启发,即一幅真正的画作需要最佳地利用色彩、透视和笔触。梵高将“有目的”一词应用于他认为已经掌握的画作,区别于他视为习作的作品。 |
1 | 0.768032 | ==== 自画像 ==== 1885 年至 1889 年间,梵高创作了超过 43 幅自画像。它们通常以系列形式完成,例如 1887 年中旬在巴黎创作的那些,并一直持续到他去世前不久。通常,这些肖像画是习作,在他不愿与他人交往或缺乏模特时创作,于是就画自己。 梵高的自画像反映了高度的自我审视。它们通常旨在标记他生活中的重要时期;例如,1887 年中旬的巴黎系列是在他意识到克劳德·莫奈、保罗·塞尚和西涅克时绘制的。在《戴灰色毡帽的自画像》中,浓重的颜料条向画布外散开。这是他那个时期最著名的自画像之一,“凭借其高度组织化的节奏笔触,以及源自新印象派风格的新颖光环,这幅画作被梵高自己称为‘有目的’的画布。” 它们包含广泛的相貌描绘。梵高的精神和身体状况通常显而易见;他可能显得不修边幅,胡子拉碴或疏于打理,眼睛深陷,下颚无力,或牙齿脱落。有些画作显示他嘴唇丰满,脸长或头骨突出,或特征锐利、警觉。他的头发有时被描绘成鲜艳的红褐色,有时则呈灰色。 梵高的自画像风格各异。在 1888 年 12 月之后绘制的那些作品中,鲜艳色彩的强烈对比突显了他憔悴的肤色。有些画作描绘了留胡子的艺术家,有些则没有。在割耳后的肖像画中,可以看到他缠着绷带。只有少数画作将他描绘成一位画家。在圣雷米创作的那些画作中,头部是从右侧描绘的,这是他受伤耳朵的对侧,因为他是通过镜子反观自己作画的。 |
2 | 0.769051 | 凭借其宽阔的笔触、富有创意的透视、色彩、轮廓和设计,这些画作代表了他所追求的风格。 === 主要系列 === 梵高的风格发展通常与他在欧洲不同地方居住的时期相关联。他倾向于沉浸在当地文化和光照条件中,尽管他始终保持着高度个性的视觉视角。他作为艺术家的发展是缓慢的,并且他对自己的绘画局限性有所了解。梵高经常搬家,也许是为了接触新的视觉刺激,并通过接触发展他的技术技能。艺术史学家 Melissa McQuillan 认为,搬家也反映了后来的风格变化,并且梵高通过搬家来避免冲突,并以此作为应对理想主义艺术家面临当时现实情况的机制。 ==== 肖像画 ==== 梵高说肖像画是他最大的兴趣。他在 1890 年写道:“在我的职业中,我最热衷的,比所有其他事物都要多得多,是肖像画,现代肖像画。”这是“绘画中唯一深深打动我并给我无限感的事物”。他给妹妹写信说,他希望画出能够流传下去的肖像画,并且他会使用色彩来捕捉他们的情感和性格,而不是追求照片般的写实。最亲近梵高的人大多没有出现在他的肖像画中;他很少画提奥、范·拉帕德或伯纳德。他母亲的肖像画是根据照片绘制的。 梵高反复描绘了阿尔勒的邮局局长约瑟夫·鲁林及其家人。在《摇篮曲》(La Berceuse) 的五个版本中,梵高描绘了奥古斯丁·鲁林静静地拿着一根绳子,摇晃着她看不见的婴儿摇篮。梵高曾计划将其作为三联画的中心图像,两侧是向日葵画作。 |
使用 RankZephyr
重排序后,正确结果在 50 个候选中排名第 1
位¶
输入 [ ]
已复制!
new_nodes = get_retrieved_nodes(
"Which date did Paul Gauguin arrive in Arles?",
vector_top_k=50,
reranker_top_n=3,
with_reranker=True,
model="rank_zephyr",
window_size=15,
)
visualize_retrieved_nodes(new_nodes)
new_nodes = get_retrieved_nodes( "Which date did Paul Gauguin arrive in Arles?", vector_top_k=50, reranker_top_n=3, with_reranker=True, model="rank_zephyr", window_size=15, ) visualize_retrieved_nodes(new_nodes)
INFO:httpx:HTTP Request: POST https://api.openai.com/v1/embeddings "HTTP/1.1 200 OK" HTTP Request: POST https://api.openai.com/v1/embeddings "HTTP/1.1 200 OK" Loading rank_zephyr ...
Loading checkpoint shards: 100%|██████████| 3/3 [00:02<00:00, 1.11it/s]
Completed loading rank_zephyr
100%|██████████| 1/1 [00:11<00:00, 11.38s/it]
得分 | 文本 | |
---|---|---|
0 | 0.857234 | 在梵高多次恳求后,高更于 10 月 23 日抵达阿尔勒,并在 11 月与他一同作画。高更在他的《向日葵画家》中描绘了梵高;梵高则根据高更的建议,凭借记忆作画。在这些“想象性”画作中,有《艾腾花园的回忆》。他们第一次联合户外写生是在阿尔勒斯康普(Alyscamps),当时他们创作了成对的画作《阿尔勒斯康普》。高更在访问期间完成的唯一一幅画作是他的梵高肖像。 1888 年 12 月,梵高和高更拜访了蒙彼利埃,在那里他们在法布尔博物馆看到了库尔贝和德拉克洛瓦的作品。他们的关系开始恶化;梵高钦佩高更,并希望被当作他的平等伙伴,但高更傲慢专横,这让梵高感到沮丧。他们经常争吵;梵高越来越害怕高更会抛弃他,而梵高形容为“过度紧张”的局面正迅速走向危机点。 \t\t \t\t\t \t\t\t \t\t \t\t \t\t\t \t\t\t \t\t \t\t \t\t\t \t\t\t \t\t \t\t \t\t\t \t\t\t \t\t \t\t \t\t\t \t\t\t \t\t ==== 阿尔勒的医院 (1888 年 12 月) ==== 导致梵高割耳的确切顺序尚不清楚。高更在十五年后表示,那一晚之前发生了几次身体威胁行为。他们的关系复杂,提奥可能欠高更钱,高更怀疑这对兄弟在经济上剥削他。似乎梵高很可能意识到高更打算离开。接下来的几天大雨,导致两人被困在黄房子里。高更回忆说,他出去散步后梵高跟着他,“手里拿着一把打开的剃刀冲向我。”这个说法未经证实;高更当晚几乎肯定不在黄房子里,很可能住在旅馆里。 在 1888 年 12 月 23 日晚上发生争执后,梵高回到自己的房间,似乎听到了声音,用剃刀割掉了左耳的全部或一部分,导致严重出血。 |
1 | 0.861649 | ==== 高更的到访 (1888) ==== 当高更同意在 1888 年访问阿尔勒时,梵高希望建立友谊并实现他艺术家集体的想法。梵高为了迎接高更的到来,在一周内画了四个版本的《向日葵》。“希望和高更一起住在我们自己的画室里,”他写信给提奥说,“我想为画室做些装饰。只有大向日葵。” 当博赫再次来访时,梵高为他画了一幅肖像,以及习作《在星空下的诗人》。 为了迎接高更的到访,梵高听从车站邮局主管约瑟夫·鲁林(他也为鲁林画过肖像)的建议,买了两个床位。9 月 17 日,他在仍旧家具稀少的黄房子里度过了第一个晚上。当高更同意和他一起在阿尔勒工作和生活后,梵高开始着手《黄房子装饰画》,这可能是他最雄心勃勃的作品。他完成了两幅椅子画:《梵高的椅子》和《高更的椅子》。 在梵高多次恳求后,高更于 10 月 23 日抵达阿尔勒,并在 11 月与他一同作画。高更在他的《向日葵画家》中描绘了梵高;梵高则根据高更的建议,凭借记忆作画。在这些“想象性”画作中,有《艾腾花园的回忆》。他们第一次联合户外写生是在阿尔勒斯康普(Alyscamps),当时他们创作了成对的画作《阿尔勒斯康普》。高更在访问期间完成的唯一一幅画作是他的梵高肖像。 1888 年 12 月,梵高和高更拜访了蒙彼利埃,在那里他们在法布尔博物馆看到了库尔贝和德拉克洛瓦的作品。他们的关系开始恶化;梵高钦佩高更,并希望被当作他的平等伙伴,但高更傲慢专横,这让梵高感到沮丧。 |
2 | 0.852035 | 高更逃离了阿尔勒,再也没有见过梵高。他们继续通信,1890 年,高更提议他们在安特卫普成立一个工作室。同时,医院的其他访客包括玛丽·吉努和鲁林。 尽管诊断悲观,梵高还是恢复了健康,并于 1889 年 1 月 7 日回到了黄房子。接下来的一个月,他在医院和家之间度过,遭受着幻觉和中毒妄想。3 月,在 30 位镇民(包括吉努一家)请愿将他描述为“红发疯子”(le fou roux)后,警方关闭了他的房子;梵高回到了医院。保罗·西涅克在 3 月份两次拜访了他;4 月,洪水损坏了他自己家里的画作后,梵高搬进了雷医生拥有的房间。两个月后,他离开阿尔勒,自愿进入圣雷米普罗旺斯的精神病院。大约在这个时候,他写道,“有时是无法形容的痛苦情绪,有时是时间的面纱和环境的宿命仿佛瞬间被撕裂的时刻。” 梵高将他 1889 年的《菲利克斯·雷医生肖像》送给了雷医生。这位医生不喜欢这幅画,用它来修理鸡舍,后来又送给了别人。2016 年,这幅肖像收藏于普希金美术馆,估计价值超过 5000 万美元。 \t\t \t\t\t \t\t\t \t\t \t\t \t\t\t \t\t\t \t\t \t\t \t\t\t \t\t\t \t\t \t\t \t\t\t \t\t\t \t\t ==== 圣雷米 (1889 年 5 月 – 1890 年 5 月) ==== 梵高在 1889 年 5 月 8 日进入圣保罗德莫索尔精神病院,由他的看护人,一位新教牧师弗雷德里克·萨尔斯陪同。圣保罗是圣雷米的一座前修道院,距离阿尔勒不到 30 公里(19 英里),由前海军医生泰奥菲尔·佩隆管理。梵高有两个带铁栏窗的房间,其中一个他用作画室。诊所及其花园成为了他画作的主要主题。 |
使用 RankVicuna, RankGPT 检索并重排序前 10 个结果¶
输入 [ ]
已复制!
# RankVicuna
new_nodes = get_retrieved_nodes(
"Which date did Paul Gauguin arrive in Arles?",
vector_top_k=10,
reranker_top_n=3,
with_reranker=True,
model="rank_vicuna",
)
# Using RankGPT
new_nodes = get_retrieved_nodes(
"Which date did Paul Gauguin arrive in Arles?",
vector_top_k=10,
reranker_top_n=3,
with_reranker=True,
model="gpt-3.5-turbo",
)
visualize_retrieved_nodes(new_nodes)
# RankVicuna new_nodes = get_retrieved_nodes( "Which date did Paul Gauguin arrive in Arles?", vector_top_k=10, reranker_top_n=3, with_reranker=True, model="rank_vicuna", ) # Using RankGPT new_nodes = get_retrieved_nodes( "Which date did Paul Gauguin arrive in Arles?", vector_top_k=10, reranker_top_n=3, with_reranker=True, model="gpt-3.5-turbo", ) visualize_retrieved_nodes(new_nodes)
对于其他模型,请使用model=
¶
monot5
用于 MonoT5 逐点重排器castorini/LiT5-Distill-base
用于 LiT5 蒸馏重排器castorini/LiT5-Score-base
用于 LiT5 分数重排器