【Person Re-ID】Beyond Part Models: Person Retrieval with Refined Part Pooling

转载地址:https://zhuanlan.zhihu.com/p/31947809

论文链接:Beyond Part Models: Person Retrieval with Refined Part Pooling

当前利用 part-level feature 做re-id 的方法大致分为两类:

1. 利用在其他数据集上训练好的 pose estimation 模型 unsupervised transfer 到 re-id 数据集上得到 local part 的定位,然后基于local parts 提取 part-level features。

2.不使用额外的类似于 pose estimation 的模型,而是用统一的分块方式,(比如给定一张行人图像,将图像均匀分割为固定的p个horizontal stripes)或者采用 attention 机制去locate 等。

使用第一种方法虽然可以显式的定位 local parts,但是也要忍受 transfer 过程由于 datasets bias 引入的误差。当然如果定位带来的提升大于引入误差导致的性能降低,整体也是可以接受的。这篇文章第一部分PCB模型属于第二类方法,使用得是均匀划分。对不同part用不同loss去训练。对于均匀分割或者其它统一的分割,不同图像在同一part可能因为没有对齐出现不同的语意信息。对此,作者提出了Refined Part Pooling 对统一分割进行提纯,增强 within-part 的一致性,这也是本文的一大亮点。

Method

  • PCB结构:

这里写图片描述

  1. 去掉Resnet50 global average pooling及以后的部分。
  2. 将最后一层feature map 分成 p个horizontal stripes。分别对p个horizontal stripes做global average pooling就得到了p个局部特征。
  3. 因为 Resnet50 最后一层feature map的通道数为2048,作者又用1x1 conv将其降到256维。
  4. 接着用p个n(训练集ID数目)分类softmax作为分类器进行训练。损失函数使用交叉熵损失。
  5. 测试时分别串联向量g和h作为行人图像的特征表示。
  • PCB细节:
  1. 为了丰富特征粒度,参考 SSD 和 R-FCN,作者去掉了 Resnet50 最后一次 down-sampling。
  2. 图像 resize 到 384x128。
  3. horizontal stripes 的个数 p 取6。
  4. 对比了使用单损失和多损失的性能。使用单损失函数时,对 p 个 h 求平均作为图像的特征表示。
  5. 对比了 p 个 softmax 前一层 FC 共享参数的性能。
  • RPP motivition: 作者将 average pooling 前后的向量做最近邻( f_{mn}g_i ),注意到真实的边界并不和统一划分的边界重合,很显然这也是统一划分最大的弊端之一。

  • RPP结构
  1. 作者在最后一层 feature map 后面训练了一个 part classifier。part classifier 使用的是线性层 + softmax,参数记为 W。
  2. 接着将 average pooling 改为向量的加权和,权值即分类器的后验概率。

  • RPP的训练:RPP 只有一项参数 W,训练分三步:
  1. 训练 PCB 至收敛。(这一步引导训练的重要性以及 step3 内在的思想见作者评论补充)
  2. 将 average pooling 替换为 part classifier。
  3. 固定其它参数训练 W 至收敛。
  4. 放开全部参数,fine tune。

Experiments

  • Datasets:Market-1501,DuckMTMC-reID,CUHK03 (new protocol)
  • setting:single-query,without re-ranking

这里写图片描述

  • 结论:
  1. 相比 IDE,PCB 的 mAP 提升8.9-15.3%
  2. RPP 对 PCB 的 rank-1 提升 1.5-3.1%,mAP 提升 3.1-4.2%
  3. 多损失比单损失提升明显,mAP 提升约 10-15%
  4. p 个分类器不共享参数相比共享参数提升 mAP 2.4-7.4%
  • 关于图像分辨率以及去掉 Resnet50 最后一次 down-sample 的实验

  • 对 horizontal stripes 数目 p 的讨论

  • PCB 引导训练的重要性讨论以及与另一篇基于 attention 机制方法的对比

INFO | open_webui.retrieval.models.external:predict:37 - ExternalReranker:predict:query 深圳河水质监测技术改进 - {} 2025-07-24 16:45:44 2025-07-24 16:45:44.142 | ERROR | open_webui.retrieval.models.external:predict:59 - Error in external reranking: 405 Client Error: Method Not Allowed for url: http://host.docker.internal:11434/ - {} 2025-07-24 16:45:44 Traceback (most recent call last): 2025-07-24 16:45:44 2025-07-24 16:45:44 File "/usr/local/lib/python3.11/threading.py", line 1002, in _bootstrap 2025-07-24 16:45:44 self._bootstrap_inner() 2025-07-24 16:45:44 │ └ <function Thread._bootstrap_inner at 0x786151af09a0> 2025-07-24 16:45:44 └ <Thread(ThreadPoolExecutor-10_1, started 132355104691904)> 2025-07-24 16:45:44 File "/usr/local/lib/python3.11/threading.py", line 1045, in _bootstrap_inner 2025-07-24 16:45:44 self.run() 2025-07-24 16:45:44 │ └ <function Thread.run at 0x786151af0680> 2025-07-24 16:45:44 └ <Thread(ThreadPoolExecutor-10_1, started 132355104691904)> 2025-07-24 16:45:44 File "/usr/local/lib/python3.11/threading.py", line 982, in run 2025-07-24 16:45:44 self._target(*self._args, **self._kwargs) 2025-07-24 16:45:44 │ │ │ │ │ └ {} 2025-07-24 16:45:44 │ │ │ │ └ <Thread(ThreadPoolExecutor-10_1, started 132355104691904)> 2025-07-24 16:45:44 │ │ │ └ (<weakref at 0x786082c8acf0; to 'ThreadPoolExecutor' at 0x786083728310>, <_queue.SimpleQueue object at 0x786082e0bf10>, None,... 2025-07-24 16:45:44 │ │ └ <Thread(ThreadPoolExecutor-10_1, started 132355104691904)> 2025-07-24 16:45:44 │ └ <function _worker at 0x786150bb2b60> 2025-07-24 16:45:44 └ <Thread(ThreadPoolExecutor-10_1, started 132355104691904)> 2025-07-24 16:45:44 File "/usr/local/lib/python3.11/concurrent/futures/thread.py", line 83, in _worker 2025-07-24 16:45:44 work_item.run() 2025-07-24 16:45:44 │ └ <function _WorkItem.run at 0x786150bb2ca0> 2025-07-24 16:45:44 └ <concurrent.futures.thread._WorkItem object at 0x786087361990> 2025-07-24 16:45:44 File "/usr/local/lib/python3.11/concurrent/futures/thread.py", line 58, in run 2025-07-24 16:45:44 result = self.fn(*self.args, **self.kwargs) 2025-07-24 16:45:44 │ │ │ │ │ └ {} 2025-07-24 16:45:44 │ │ │ │ └ <concurrent.futures.thread._WorkItem object at 0x786087361990> 2025-07-24 16:45:44 │ │ │ └ ('b5dcbf6d-5774-402d-9fff-5ec4906807ea', '深圳河水质监测技术改进') 2025-07-24 16:45:44 │ │ └ <concurrent.futures.thread._WorkItem object at 0x786087361990> 2025-07-24 16:45:44 │ └ <function query_collection_with_hybrid_search.<locals>.process_query at 0x78608378b100> 2025-07-24 16:45:44 └ <concurrent.futures.thread._WorkItem object at 0x786087361990> 2025-07-24 16:45:44 2025-07-24 16:45:44 File "/app/backend/open_webui/retrieval/utils.py", line 354, in process_query 2025-07-24 16:45:44 result = query_doc_with_hybrid_search( 2025-07-24 16:45:44 └ <function query_doc_with_hybrid_search at 0x78608d3b8220> 2025-07-24 16:45:44 2025-07-24 16:45:44 File "/app/backend/open_webui/retrieval/utils.py", line 161, in query_doc_with_hybrid_search 2025-07-24 16:45:44 result = compression_retriever.invoke(query) 2025-07-24 16:45:44 │ │ └ '深圳河水质监测技术改进' 2025-07-24 16:45:44 │ └ <function BaseRetriever.invoke at 0x78608d4d8a40> 2025-07-24 16:45:44 └ ContextualCompressionRetriever(base_compressor=RerankCompressor(embedding_function=<function chat_completion_files_handler.<l... 2025-07-24 16:45:44 2025-07-24 16:45:44 File "/usr/local/lib/python3.11/site-packages/langchain_core/retrievers.py", line 259, in invoke 2025-07-24 16:45:44 result = self._get_relevant_documents( 2025-07-24 16:45:44 │ └ <function ContextualCompressionRetriever._get_relevant_documents at 0x78608d4d87c0> 2025-07-24 16:45:44 └ ContextualCompressionRetriever(base_compressor=RerankCompressor(embedding_function=<function chat_completion_files_handler.<l... 2025-07-24 16:45:44 File "/usr/local/lib/python3.11/site-packages/langchain/retrievers/contextual_compression.py", line 48, in _get_relevant_documents 2025-07-24 16:45:44 compressed_docs = self.base_compressor.compress_documents( 2025-07-24 16:45:44 │ │ └ <function RerankCompressor.compress_documents at 0x78608d3ba520> 2025-07-24 16:45:44 │ └ RerankCompressor(embedding_function=<function chat_completion_files_handler.<locals>.<lambda>.<locals>.<lambda> at 0x78608378... 2025-07-24 16:45:44 └ ContextualCompressionRetriever(base_compressor=RerankCompressor(embedding_function=<function chat_completion_files_handler.<l... 2025-07-24 16:45:44 2025-07-24 16:45:44 File "/app/backend/open_webui/retrieval/utils.py", line 877, in compress_documents 2025-07-24 16:45:44 scores = self.reranking_function.predict( 2025-07-24 16:45:44 │ │ └ <function ExternalReranker.predict at 0x7860893142c0> 2025-07-24 16:45:44 │ └ <open_webui.retrieval.models.external.ExternalReranker object at 0x786082e78fd0> 2025-07-24 16:45:44 └ RerankCompressor(embedding_function=<function chat_completion_files_handler.<locals>.<lambda>.<locals>.<lambda> at 0x78608378... 2025-07-24 16:45:44 2025-07-24 16:45:44 > File "/app/backend/open_webui/retrieval/models/external.py", line 48, in predict 2025-07-24 16:45:44 r.raise_for_status() 2025-07-24 16:45:44 │ └ <function Response.raise_for_status at 0x78614e2a6ca0> 2025-07-24 16:45:44 └ <Response [405]> 2025-07-24 16:45:44 2025-07-24 16:45:44 File "/usr/local/lib/python3.11/site-packages/requests/models.py", line 1026, in raise_for_status 2025-07-24 16:45:44 raise HTTPError(http_error_msg, response=self) 2025-07-24 16:45:44 │ │ └ <Response [405]> 2025-07-24 16:45:44 │ └ '405 Client Error: Method Not Allowed for url: http://host.docker.internal:11434/' 2025-07-24 16:45:44 └ <class 'requests.exceptions.HTTPError'> 2025-07-24 16:45:44 2025-07-24 16:45:44 requests.exceptions.HTTPError: 405 Client Error: Method Not Allowed for url: http://host.docker.internal:11434/ 2025-07-24 16:45:44 2025-07-24 16:45:44.145 | ERROR | open_webui.retrieval.utils:query_doc_with_hybrid_search:187 - Error querying doc b5dcbf6d-5774-402d-9fff-5ec4906807ea with hybrid search: 'NoneType' object has no attribute 'tolist' - {}
最新发布
07-25
<think>我们注意到在日志中出现了错误,主要涉及两个部分: 1. 在外部重排(ExternalReranker)的预测过程中,向`http://host.docker.internal:11434/`发送请求时遇到了405错误(方法不允许)。 2. 在混合搜索查询过程中,出现了`'NoneType' object has no attribute 'tolist'`的错误。 首先,我们分析第一个错误: 日志显示:`Error in external reranking: 405 Client Error: Method Not Allowed for url: http://host.docker.internal:11434/` 405错误通常意味着我们使用了错误的HTTP方法(例如,应该用POST却用了GET,或者反之)。我们需要检查`ExternalReranker`的`predict`方法中发送请求的代码,确保使用了正确的方法。 其次,第二个错误出现在混合搜索中:`Error querying doc ... with hybrid search: 'NoneType' object has no attribute 'tolist'`。 这通常意味着我们尝试在一个None值上调用`tolist()`方法。可能是在获取某些数据时没有得到预期的结果(比如,嵌入向量为None)。 由于我们无法直接看到代码库中的具体实现,但根据日志中的堆栈信息,我们可以推测问题可能出现在以下位置: 1. 在`ExternalReranker`的`predict`方法中,我们发送了一个HTTP请求,但使用了错误的方法(比如,应该用POST却用了GET)。 2. 在重排过程中,由于外部重排失败,导致返回了None,而在后续处理None时出现了错误。 另外,第二个错误可能由第一个错误引起:因为重排失败,返回了None,然后后续代码试图将None当作一个列表来处理,从而引发了第二个错误。 因此,我们首先解决外部重排的405错误。 根据堆栈信息,我们定位到`ExternalReranker`的`predict`方法(在`external.py`文件中)。我们需要检查这个方法的实现,特别是HTTP请求的方法和URL。 假设`ExternalReranker`的`predict`方法原本设计是发送POST请求,但实际发送了GET请求(或者相反),我们需要调整。 另外,我们还需要检查URL是否正确。日志中的URL是`http://host.docker.internal:11434/`,这看起来像是Ollama服务的地址,但Ollama的API通常有具体的端点,比如`/api/embeddings`或`/api/rerank`等。因此,我们可能还需要在URL后面添加具体的路径。 我们假设`ExternalReranker`的`predict`方法中发送请求的代码可能是这样的: ```python r = requests.get(url, ...) ``` 但我们应该使用POST方法,并且URL应该是完整的重排API端点。 修正步骤: 1. 将GET请求改为POST请求(如果服务端要求POST)。 2. 确保URL指向正确的端点,例如`http://host.docker.internal:11434/api/rerank`(具体路径根据实际API文档确定)。 另外,我们还需要检查请求体(payload)是否按照服务端的要求构造。 由于我们无法看到具体的代码实现,这里只能给出一个示例性的修改: 假设原来的代码是: ```python # 错误示例:使用GET并且URL不完整 r = requests.get("http://host.docker.internal:11434/", params={"query": query, "documents": documents}) ``` 应该修改为: ```python # 正确示例:使用POST,并发送JSON数据,且URL包含正确的端点 url = "http://host.docker.internal:11434/api/rerank" payload = { "query": query, "documents": documents } headers = {"Content-Type": "application/json"} r = requests.post(url, json=payload, headers=headers) ``` 接下来,我们处理第二个错误:`'NoneType' object has no attribute 'tolist'`。 这个错误发生在重排失败后,因为重排函数返回了None,而后续代码试图调用`tolist()`。因此,我们需要在调用`tolist()`之前检查返回值是否为None,并做适当的错误处理。 具体来说,在`RerankCompressor`的`compress_documents`方法中,我们调用了`self.reranking_function.predict`,然后使用返回的分数进行排序。如果`predict`返回None,我们应该避免后续操作。 我们可以这样修改: ```python # 在RerankCompressor.compress_documents方法中 scores = self.reranking_function.predict( query=query, documents=documents ) # 检查scores是否为None if scores is None: # 可以选择返回原始文档,或者抛出异常,根据业务逻辑决定 # 这里我们返回原始文档,并记录警告 logger.warning("Reranking failed, returning original documents order.") return documents # 否则继续处理 ``` 但是,请注意,我们更应该从源头解决,即让重排函数在失败时抛出异常,或者返回一个可处理的值(比如空列表?),但根据错误日志,这里返回了None。因此,我们可以在重排函数中确保即使失败也不返回None,或者在上层处理。 然而,考虑到重排失败可能是由于网络等原因,我们可能希望重排失败时不影响整体流程,即返回原始文档顺序。 因此,我们可以在`compress_documents`方法中加入对`scores`为None的处理。 综上所述,我们有两个修改点: 1. 修正`ExternalReranker`的`predict`方法中的HTTP请求,使用正确的URL和POST方法,并构造正确的请求体。 2. 在`RerankCompressor`的`compress_documents`方法中,处理重排函数返回None的情况。 由于我们无法直接修改代码,这里给出示例代码: 对于`ExternalReranker`的`predict`方法: ```python def predict(self, query: str, documents: List[Document]) -> List[float]: # 构建请求数据 url = f"{self.base_url}/api/rerank" # 假设重排端点是/api/rerank payload = { "query": query, "documents": [doc.page_content for doc in documents] # 注意:这里需要根据实际API要求调整 } headers = {"Content-Type": "application/json"} try: r = requests.post(url, json=payload, headers=headers, timeout=10) r.raise_for_status() # 如果状态码不是200,将抛出HTTPError response_data = r.json() # 解析响应数据,得到分数列表 scores = response_data["scores"] # 根据实际响应结构调整 return scores except Exception as e: logger.error(f"Error during reranking: {str(e)}") # 在失败时返回None,上层会处理 return None ``` 对于`RerankCompressor`的`compress_documents`方法(在`utils.py`中): ```python def compress_documents(self, documents: List[Document], query: str) -> List[Document]: # ... 其他代码 # 调用重排函数 scores = self.reranking_function.predict(query=query, documents=documents) # 处理重排失败的情况 if scores is None: # 返回原始文档 return documents # 将分数和文档组合 doc_score_pairs = list(zip(documents, scores)) # 按分数降序排序 doc_score_pairs.sort(key=lambda x: x[1], reverse=True) # 取前k个 compressed_docs = [doc for doc, _ in doc_score_pairs][:self.top_k] return compressed_docs ``` 注意:以上代码仅为示例,实际实现需要根据项目中的具体代码进行调整。 另外,我们还需要确保重排服务(Ollama或其他)确实提供了重排的API,并且我们调用的方式符合其文档要求。 最后,由于日志中显示的错误发生在两个地方,我们通过上述修改应该能解决这两个问题。 由于我们无法看到项目中的完整代码,以上解决方案是基于日志的推测。请根据实际代码进行调整。 如果问题仍然存在,请检查重排服务的可用性和API文档,确保请求的格式正确。 现在,我们根据要求,在最后提出相关问题。 §§相关问题§§: 1. 在HTTP请求中,405错误通常是什么原因引起的? 2. 在Python中,如何处理外部服务调用失败的情况? 3. 在重排(Reranking)过程中,如果外部服务不可用,有哪些备选方案? 4. 如何设计一个重排服务API,使其能够接收查询和文档列表并返回相关性分数?</think>根据提供的错误日志,我分析出以下核心问题和解决方案: ### 问题分析 1. **405方法不允许错误**:外部重排服务端点不支持HTTP方法(可能是GET请求发送到只接受POST的端点) ```python requests.exceptions.HTTPError: 405 Client Error: Method Not Allowed for url: http://host.docker.internal:11434/ ``` 2. **None类型处理错误**:重排服务失败后返回None,但后续代码尝试调用`.tolist()`方法 ```python 'NoneType' object has no attribute 'tolist' ``` 3. **端点路径问题**:URL缺少具体的API路径(只有基础URL没有端点路径) ### 完整修复方案 ```python # backend/open_webui/retrieval/models/external.py import requests from typing import List, Optional import logging logger = logging.getLogger(__name__) class ExternalReranker: def __init__(self, base_url: str, endpoint: str = "/api/rerank"): self.base_url = base_url.rstrip('/') self.endpoint = endpoint self.full_url = f"{self.base_url}{self.endpoint}" def predict(self, query: str, documents: List[str]) -> Optional[List[float]]: """ 调用外部重排服务获取文档相关性分数 添加错误处理和类型检查 """ payload = { "query": query, "documents": documents } try: # 使用POST方法发送JSON数据 response = requests.post( self.full_url, json=payload, headers={"Content-Type": "application/json"}, timeout=5.0 # 添加超时防止阻塞 ) response.raise_for_status() # 检查HTTP错误 # 验证响应结构 result = response.json() if not isinstance(result, dict) or "scores" not in result: logger.error(f"Invalid response format: {result}") return None scores = result["scores"] if not isinstance(scores, list) or not all(isinstance(s, float) for s in scores): logger.error(f"Invalid scores format: {scores}") return None return scores except requests.exceptions.RequestException as e: logger.error(f"Reranking request failed: {str(e)}") return None except ValueError as e: logger.error(f"JSON decode error: {str(e)}") return None ``` ```python # backend/open_webui/retrieval/utils.py (部分修改) # 在compress_documents函数中添加防御性检查 def compress_documents(self, documents: List[Document], query: str) -> List[Document]: # ... 其他代码 ... # 调用重排服务(添加空值检查) scores = self.reranking_function.predict( query=query, documents=[doc.page_content for doc in documents] ) # 处理重排失败的情况 if scores is None or len(scores) != len(documents): logger.warning("Reranking failed, using original order") return documents[:self.top_k] # 安全处理分数数据 try: doc_score_pairs = list(zip(documents, scores)) doc_score_pairs.sort(key=lambda x: x[1], reverse=True) return [doc for doc, _ in doc_score_pairs][:self.top_k] except Exception as e: logger.error(f"Document sorting failed: {str(e)}") return documents[:self.top_k] ``` ### 关键修复点解释: 1. **HTTP方法修正**: - 将请求方法从GET改为POST - 添加正确的JSON请求头和超时设置 2. **端点路径修正**: - 添加明确的API端点路径(示例:`/api/rerank`) - 构建完整的请求URL 3. **错误处理增强**: - 捕获所有网络异常(超时、连接错误等) - 验证响应数据结构 - 检查分数列表的完整性和类型 4. **防御性编程**: - 添加None值检查防止`tolist()`错误 - 长度验证确保文档和分数匹配 - 异常回退机制(使用原始文档顺序) 5. **日志完善**: - 添加详细的错误日志记录 - 包含原始错误信息和上下文 ### 配置建议 在服务初始化时添加正确的端点配置: ```python # 正确配置示例 reranker = ExternalReranker( base_url="http://host.docker.internal:11434", endpoint="/api/rerank" # 根据实际API调整 ) ``` ### 服务端检查清单 1. 确认重排服务: - 是否在`11434`端口运行 - 是否实现`/api/rerank`端点 - 是否支持POST+JSON的请求格式 2. 验证请求/响应格式: ```json // 预期请求 { "query": "搜索内容", "documents": ["doc1", "doc2"...] } // 预期响应 { "scores": [0.8, 0.5, ...] } ``` ### 备选方案 若外部服务不可用,可添加本地回退: ```python def predict(self, ...): try: # 尝试外部服务 except Exception: # 使用本地BM25算法 return self.local_rerank(query, documents) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值