美团外卖搜索基于Elasticsearch的优化实践--图文解析

美团外卖搜索基于Elasticsearch的优化实践–图文解析

前言

美团在外卖搜索业务场景中大规模地使用了 Elasticsearch 作为底层检索引擎,随着业务量越来越大,检索速度变慢了,CPU快累趴了,所以要进行优化。经过检测,发现最耗资源(性能热点)的地方是倒排链的检索与合并,**Run-length Encoding(RLE)**技术设计实现了一套高效的倒排索引,使倒排链合并时间降低了 96%。将这一索引能力开发成了一款通用插件集成到 Elasticsearch 中,使得 Elasticsearch 的检索链路时延降低了 84%。

离线数仓

离线数仓之所以被称为“离线”,是因为它处理的数据不是实时的,而是在一定时间周期内进行计算和存储。这与实时数据仓库(例如实时流数据)不同,后者可以立即处理和分析数据。所以,离线数仓更像是一个历史数据的“存储室”,用于长期分析和决策。

知名:Snowflake,Amazon Redshift,Google BigQuery等

近线检索:

近线检索是一种根据数据的相似性,在数据库中寻找与目标数据最相似的项目的技术。这个技术认为,特征数据的值约相近,之间的相似性就越高。

B端检索与C端检索:面向商家,企业,团队的检索。举例:商城app首页的搜索框是面向普通用户的C端检索,用来检索商品。而商城后台的运维等人员,商家检索该平台或同类别网店的历史数据进行决策分析等操作,属于B端检索。

倒排索引

文中多次提到的 Posting List就是指的倒排链
通过一个单词,找到含有单词有关系的文章,是倒排索引

下图中包括程序员,产品经理,运维以及对应的docId的表就是倒排所以,其中每一个分词(或叫做词条,文中的Term)所以应的DocId就是该Term的倒排链:

在这里插入图片描述

为了增加查询效率,节省磁盘与内存资源,增加了字典树 term index概念:

在这里插入图片描述

倒排链的合并

如果用户的查询包含多个关键词,搜索引擎会合并这些关键词的倒排列表,找到同时包含这些关键词的文档。

在这里插入图片描述

背景

当前,外卖搜索主要为 Elasticsearch,具有较强的 Location Based Service(LBS) 依赖(其实就是地理位置有限制,太远了送不了),即用户所能点餐的商家,是由商家配送范围决定的。对于每一个商家的配送范围,大多采用多组电子围栏进行配送距离的圈定,一个商家存在多组电子围栏,并且随着业务的变化会动态选择不同的配送范围,电子围栏示意图如下:

图1 电子围栏示意图

  1. 电子围栏
    • 定义:电子围栏是一种虚拟的地理边界,可以通过定位系统或其他位置服务技术来创建。
    • 作用:商家可以使用电子围栏来限定配送范围,确保配送员在指定区域内进行配送。以及用来限制用户在自己的可配送范围内,搜索到可配送范围内的商家对应的商品。
  2. 多组电子围栏动态选择不同的配送范围
    • 定义:商家可能需要不止一个电子围栏来定义不同的配送区域,用来应对可能会随着时间和情况变化的业务需求。
    • 例子:商家在城南开了一家烤串店,如果城北的用户点餐外卖,则会触发更大范围的电子围栏,提高配送费用等。

原文:

考虑到商家配送区域动态变更带来的问题,通过上游一组 R-tree 服务判定可配送的商家列表来进行外卖搜索。因此,LBS 场景下的一次商品检索,可以转化为如下的一次 Elasticsearch 搜索请求:

POST food/_search
{
   "query": {
      "bool": {
         "must":{
            "term": { "spu_name": { "value": "烤鸭"} }
           //...
         },
         "filter":{
           "terms": {
              "wm_poi_id": [1,3,18,27,28,29,...,37465542] // 上万
            }
         }
      }
   }
  //...
}

R-tree

B树向多维空间发展的另一种形式

先说B树:

图片来自百度百科

比如我要查找id是38的数据,范围在28-65之间,选中28的p2,发现了范围在35-65之间,选择35的p2最后快速找到了38,而且是比SSD还要快的多的顺序查找。

img

R树和B树有点像:

图片来自百度百科

A和B是两个大的区域,里面包含了若干小区域,在R树中可以通过下图呈现(注意我们发现A和B其实里面有重叠的部分):

img

用地图的例子来解释,就是所有的数据都是餐厅所对应的地点,先把相邻的餐厅划分到同一块区域,划分好所有餐厅之后,再把邻近的区域划分到更大的区域,划分完毕后再次进行更高层次的划分,直到划分到只剩下两个最大的区域为止。要查找的时候就方便了。

下图以及进一步详细解释的内容来自:

https://blog.csdn.net/wzf1993/article/details/79547037

img

最后在解释一下如果发现重合区域怎么办:

  • S1:[查找子树] 如果T是非叶子结点,如果T所对应的矩形与S有重合,那么检查所有T中存储的条目,对于所有这些条目,使用Search操作作用在每一个条目所指向的子树的根结点上(即T结点的孩子结点)。

    简单说就是两个同级别非叶子节点有重合区域了,检测已存储对应的子节点,检查指向到底是哪一个。

  • S2:[查找叶子结点] 如果T是叶子结点,如果T所对应的矩形与S有重合,那么直接检查S所指向的所有记录条目。返回符合条件的记录。

    简单说就是父子节点有重合,检查父节点的条目看看有没有这个子节点

这里要注意的是Elasticsearch本身并不提供R-Tree功能,原文中也说了是上游提供的功能,需要先在程序中找到用户所在区域的叶子节点,再找到里面的所有商家,之后进行搜索。

POST food/_search
{
   "query": {
      "bool": {
         "must":{
            "term": { "spu_name": { "value": "烤鸭"} }
           //...
         },
         "filter":{
           "terms": {
              "wm_poi_id": [1,3,18,27,28,29,...,37465542] // 上万
            }
         }
      }
   }
  //...
}

代码中的wm_poi_id可能是使用到了Elasticsearch中的父子关系映射,用来模拟R-Tree,为了更好理解,下面例子中是一家店与这家店菜品的模拟

PUT /food/restaurants/1
{
   "name": "烤鸭之家",
   "address": "北京市朝阳区XXX路123号",
   "phone": "123-456-7890",
   "wm_poi_id": 1
}

POST /food/dishes
{
   "wm_poi_id": 1, // 父文档的 wm_poi_id
   "spu_name": "北京烤鸭",
   "price": 88.0,
   "description": "传统的烤鸭,外酥里嫩,美味可口。"
}

POST /food/dishes
{
   "wm_poi_id": 1,
   "spu_name": "葱爆牛肉",
   "price": 58.0,
   "description": "香辣的葱爆牛肉,下饭绝佳。"
}

原文:对于一个通用的检索引擎而言,Terms 检索非常高效,平均到每个 Term 查询耗时不到0.001 ms。因此在早期时,这一套架构和检索 DSL(ES的领域专用语言) 可以很好地支持美团的搜索业务——耗时和资源开销尚在接受范围内。然而随着数据和供给的增长,一些供给丰富区域的附近可配送门店可以达到 20000~30000 家,这导致性能与资源问题逐渐凸显。这种万级别的 Terms 检索的性能与耗时已然无法忽略,仅仅这一句检索就需要 5~10 ms

挑战及问题

倒排链查询流程

1,从内存中的 Term Index 中获取该 Term 所在的 Block 在磁盘上的位置。

“Block” 指的是磁盘上存储相关词条信息的数据块。这个概念与数据库中的"块"或"页"(通常是数据存储的基本单位)有些相似。在 Elasticsearch 中,倒排索引由多个这样的 Block 组成,每个 Block 包含了一组词条的信息,以及这些词条在文档中出现的位置。为了提高性能,同时又节约内存空间,所以不会在内存中直接存储词条对应的信息,而是存储一个block的位置,通过这个位置到硬盘上去查找具体的位置以及数据信息。

2,从磁盘中将该 Block 的 TermDictionary 读取进内存。

Block存储的所有词条信息被统称为TermDictionary,Elasticsearch 在处理查询时,通过加载整个 Block 来提高效率,因为通常情况下,相关的词条会被存储在相同或相近的位置。在TermDictionary中,存放的基本都是该词条以及和该词条相关的词条内容。

3,对倒排链存储格式的进行 Decode,生成可用于合并的倒排链。

倒排链的存储格式通常是压缩的,为了使用这些数据,需要先对它们进行解码也就是Decode。解码后就可以得到一个可用于查询合并的倒排链,包含了所有包含该词条的文档ID的列表。

原文:

可以看到,这一查询流程非常复杂且耗时,且各个阶段的复杂度都不容忽视。所有的 Term 在索引中有序存储,通过二分查找找到目标 Term。这个有序的 Term 列表就是 TermDictionary ,二分查找 Term 的时间复杂度为 O(logN) ,其中 N 是 Term 的总数量 。Lucene 采用 Finite State Transducer(FST)对 TermDictionary 进行编码构建 Term Index。FST 可对 Term 的公共前缀、公共后缀进行拆分保存,大大压缩了 TermDictionary 的体积,提高了内存效率,FST 的检索速度是 O(len(term)),其对于 M 个 Term 的检索复杂度为 O(M * len(term))。

公共前缀:如果多个词条有相同的开始部分,比如 “search”, “seek”, 和 “seem” 都以 “se” 开头,FST 会将 “se” 存储一次,然后链接到不同的后续字符。
公共后缀:同样地,如果多个词条在结尾处相同,FST 也会对这些后缀进行合并存储。
通过这种方式,FST 可以大大减少存储每个独立词条所需的空间,因为它只存储每个唯一的前缀和后缀一次。

O(len(term)):因为前缀与后缀的原因,查找速度与词条的长度有关。

前缀类似于这张图。

在这里插入图片描述

倒排链合并流程

在经过上述的查询,检索出所有目标 Term 的 Posting List 后,需要对这些 Posting List 求并集(OR 操作)。在 Lucene 的开源实现中,其采用 Bitset 作为倒排链合并的容器,然后遍历所有倒排链中的每一个文档,将其加入 DocIdSet 中。

伪代码如下:

Input:  termsEnum: 倒排表;termIterator:候选的term
Output: docIdSet : final docs set
for term in termIterator:  //遍历倒排表中的每一个词条
  if termsEnum.seekExact(term) != null: //如果存在,不为空
     docs = read_disk()  // 磁盘读取
     docs = decode(docs) // 倒排链的decode流程,解压缩,找到这个词条的倒排链
     for doc in docs:
        docIdSet.or(doc) //代码实现为DocIdSetBuilder.add。 //将倒排链中存放的doc信息存入docIdSet
end for
docIdSet.build()//合并,排序,最终生成DocIdSetBuilder,对应火焰图最后一段。

原生es倒排链合并问题:

假设我们有 M 个 Term,每个 Term 对应倒排链的平均长度为 K,那么合并这 M 个倒排链的时间复杂度为:O(K * M + log(K * M))。 可以看出倒排链合并的时间复杂度与 Terms 的数量 M 呈线性相关。在我们的场景下,假设一个商家平均有一千个商品,一次搜索请求需要对一万个商家进行检索,那么最终需要合并一千万个商品,即循环执行一千万次,导致这一问题被放大至无法被忽略的程度。

下图说明了查找,读取,合并的火焰图确实存在高耗时。

图2 profile 火焰图

PS火焰图

在这里插入图片描述

技术探索与实践

倒排链查询优化

默认使用FST本身也是不错的选择,但在原文提到的背景中有些不适用,原因如下:

考虑到在外卖搜索场景有以下几个特性:

  • Term 的数据类型为 long 类型。(可以巧妙利用long类型特有的压缩算法)
  • 无范围检索,均为完全匹配。
  • 无前缀匹配、模糊查找的需求,不需要使用前缀树相关的特性。(用不着FTS的特性)
  • 候选数量可控,每个商家的商品数量较多,即 Term 规模可预期,内存可以承载这个数量级的数据。(我家内存够大

最终方案:时间换空间,选择了查询复杂度O(1)的哈希表LongObjectHashMap,和传统的HashMap相比,LongObjectHashMap 可以存储更多的数据,对Long类型友好。

在这里插入图片描述

倒排链合并

原生实现
RoaringBitmap

原文

当前 Elasticsearch 选择 RoaringBitMap 做为 Query Cache 的底层数据结构缓存倒排链,加快查询速率。

RoaringBitmap 是一种压缩的位图,相较于常规的压缩位图能提供更好的压缩,在稀疏数据的场景下空间更有优势。以存放 Integer 为例,Roaring Bitmap 会对存入的数据进行分桶,每个桶都有自己对应的 Container。在存入一个32位的整数时,它会把整数划分为高 16 位和低 16 位,其中高 16 位决定该数据需要被分至哪个桶,我们只需要存储这个数据剩余的低 16 位,将低 16 位存储到 Container 中,若当前桶不存在数据,直接存储 null 节省空间。 RoaringBitmap有不同的实现方式,下面以 Lucene 实现(RoaringDocIdSet)进行详细讲解:

如原理图中所示,RoaringBitmap 中存在两种不同的 Container:Bitmap Container 和 Array Container。

图3 Elasticsearch中Roaringbitmap的示意图

这两种 Container 分别对应不同的数据场景——若一个 Container 中的数据量小于 4096 个时,使用 Array Container 来存储。当 Array Container 中存放的数据量大于 4096 时,Roaring Bitmap 会将 Array Container 转为 Bitmap Container。即 Array Container 用于存放稀疏数据,而 Bitmap Container 用于存放稠密数据,这样做是为了充分利用空间。下图给出了随着容量增长 Array Container 和 Bitmap Container 的空间占用对比图,当元素个数达到 4096 后(每个元素占用 16 bit ),Array Container 的空间要大于 Bitmap Container。

针对上面原文的解析

要了解RoaringBitmap,首先要简单了解下Bitmap:

假设要缓存这样一些数字到Bitmap中: 2,5,7,9

在这里插入图片描述

首先,创建一个数组,长度为要缓存数字的最大值+1,例子中最大数字是9,则创建的数组长度为9+1=10

数组中只存放0或1,为1表示存放的数字存在,0表示不存在

因为数组下标是从0开始的,实际的值就是数组值为1的下标值+1

Bitmap有一个很严重的缺陷就是:如果数据稀疏,比如1,2,3,999999999999,一共四个数字,但要创建一个特别特别大的数组来存放,中间有一大段数组值为0,数据稀疏问题需要通过RoaringBitmap来解决。

RoaringBitmap优化Bitmap

1.将存储容器分为Bitmap Container(位图容器) 和 Array Container(数组容器),数据量小于4096使用数组容器

数据量大于4096使用位图容器

其中的位图容器和传统bitmap特性相似,用来存储应对数据稠密场景,而稀疏数据通过数组存储。在储存时,RoaringBitmap算法会安排数组容器适当的占用位图容器中的稀疏空间,从而更小有效的利用内存。

图3 Elasticsearch中Roaringbitmap的示意图

原文中的分桶,高16位,低16位又是什么意思呢:

假设我们有一些整数:65537, 65538, 131073。在 RoaringBitmap 中,这些数会被分为高16位和低16位:

对于 65537(二进制为 00000001 00000000 00000001),高16位是 00000001 00000000(即1),低16位是 00000001(即1)。
对于 65538(二进制为 00000001 00000000 00000010),高16位是 00000001 00000000(即1),低16位是 00000010(即2)。
对于 131073(二进制为 00000010 00000000 00000001),高16位是 00000010 00000000(即2),低16位是 00000001(即1)。
在这个例子中,65537 和 65538 会被放入编号为1的"桶"中,而 131073 会被放入编号为2的"桶"中。这样,当我们需要查找一个数是否存在时,我们先看它的高16位,确定去哪个"桶"查找,然后在那个"桶"里用低16位找到具体的数。这种方法使得存储更加紧凑,检索也更快速。

Index Sorting

这个比较好理解,就是说Elasticsearch 从 6.0 版本开始在索引阶段可以配置多个字段进行排序了。

在这里插入图片描述

基于 RLE 的倒排格式设计

Run-Length Encoding

原理:对于完全连续数字{1,2,3,4,5,6,7,8,9,10} —>经过RLE压缩为—>{1,10} 也就是{start,length}

RoaringBitmap对于美团外卖场景仍然需要进一步优化,通过使用Index Sorting对多个字段进行排序后得到一个突破性进展,即通过一些字段进行排序后,商家的商品ID完全连续了。

在 bitmap 这一场景之下,主要通过压缩连续区间的稠密数据,节省内存开销。以数组 [1, 2, 3, …, 59, 60, 89, 90, 91, …, 99, 100] 为例(如下图上半部分):使用 RLE 编码之后就变为 [1, 60, 89, 12]——形如 [start1, length1, start2, length2, …] 的形式,其中第一位为连续数字的起始值,第二位为其长度。

同时还对倒排链的合并做了修改,由原来的单文档按序插入优化为批量合并,最终通过Index Sorting排序后,通过RLE编码对完全连续的数字进行了压缩:

图5 倒排链Merge方式的演进

SparseRoaringDocIdSet

RoaringBitmap中存在分桶操作,如下图,可能存在有些桶是空的,美团技术团队通过实现SparseRoaringDocIdSet,额外使用一组索引记录所有有值的 桶的 位置。

原文也介绍了ES也通过算法优化节约开销了:在 Elasticsearch 场景上,RoaringDocIdSet 在申请 bucket 容器的数组时,会根据当前 Segment 中的最大 Doc ID 来申请**,**这种方式可以避免每次均按照 Integer.MAX-1 来创建容器带来的无谓开销。

图6 SparseRoaringDocIdSet 编排

经过上述修改,倒排链合并耗时节约了96%

图7 倒排链Merge性能比对

最终将上述优化集成到ES中,以及做了若干调整上线测试后,全链路的检索时延(TP99)降低了 84%(100 ms 下降至 16 ms),解决了外卖搜索的检索耗时问题,并且线上服务的 CPU 也大大降低。

位置。

原文也介绍了ES也通过算法优化节约开销了:在 Elasticsearch 场景上,RoaringDocIdSet 在申请 bucket 容器的数组时,会根据当前 Segment 中的最大 Doc ID 来申请**,**这种方式可以避免每次均按照 Integer.MAX-1 来创建容器带来的无谓开销。

[外链图片转存中…(img-TzaAhAdI-1719651983203)]

经过上述修改,倒排链合并耗时节约了96%

[外链图片转存中…(img-JsIhX5W7-1719651983204)]

最终将上述优化集成到ES中,以及做了若干调整上线测试后,全链路的检索时延(TP99)降低了 84%(100 ms 下降至 16 ms),解决了外卖搜索的检索耗时问题,并且线上服务的 CPU 也大大降低。

图10 完整链路查询耗时

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/760410.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

智慧校园-办公管理系统总体概述

智慧校园行政办公系统是专为高校及教育机构定制的数字化办公解决方案,它整合了众多办公应用与服务,旨在全面提升校园行政管理的效率与便捷性,推动信息的自由流动,实现绿色无纸化办公环境。该系统作为一个综合平台,将日…

redis实战-缓存穿透问题及解决方案

定义理解 缓存穿透:缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远都不会生效(只有数据库查到了,才会让redis缓存,但现在的问题是查不到),会频繁的去访问数据库。 解决…

【Spring】DAO 和 Repository 的区别

DAO 和 Repository 的区别 1.概述2.DAO 模式2.1 User2.2 UserDao2.3 UserDaoImpl 3.Repository 模式3.1 UserRepository3.2 UserRepositoryImpl 4.具有多个 DAO 的 Repository 模式4.1 Tweet4.2 TweetDao 和 TweetDaoImpl4.3 增强 User 域4.4 UserRepositoryImpl 5.比较两种模式…

RabbitMQ实践——临时队列

临时队列是一种自动删除队列。当这个队列被创建后,如果没有消费者监听,则会一直存在,还可以不断向其发布消息。但是一旦的消费者开始监听,然后断开监听后,它就会被自动删除。 新建自动删除队列 我们创建一个名字叫qu…

【CodinGame】CLASH OF CODE - 20240630

前言 本文是CodinGame(图片来自此)随手做的几个,供记录用 要求: 代码 import math import syss input()for n in range(len(s)):print(s[n:])要求 代码 import sys import math# Auto-generated code below aims at helpi…

大模型压缩量化方案怎么选?无问芯穹Qllm-Eval量化方案全面评估:多模型、多参数、多维度

基于 Transformer架构的大型语言模型在各种基准测试中展现出优异性能,但数百亿、千亿乃至万亿量级的参数规模会带来高昂的服务成本。例如GPT-3有1750亿参数,采用FP16存储,模型大小约为350GB,而即使是英伟达最新的B200 GPU 内存也只…

SpringBoot使用redis 笔记(视频摘抄 哔哩哔哩博主(感谢!):遇见狂神)

springboot集成redis步骤 1.创建springboot项目 2.配置连接 3.测试 创建springboot项目 创建以一个Maven项目 创建之后查看pom.xml配置文件,可以看到 pom文件里面导入了 data-redis 的依赖,那我们就可以在知道,springboot集成redis操作…

详解flink sql, calcite logical转flink logical

文章目录 背景示例FlinkLogicalCalcConverterBatchPhysicalCalcRuleStreamPhysicalCalcRule其它算子FlinkLogicalAggregateFlinkLogicalCorrelateFlinkLogicalDataStreamTableScanFlinkLogicalDistributionFlinkLogicalExpandFlinkLogicalIntermediateTableScanFlinkLogicalInt…

20240623日志:大模型压缩-sliceGPT

context 1. 剪枝方案图释2. 正交矩阵Q 1. 剪枝方案图释 Fig. 1.1 剪枝方案 图中的阴影是表示丢弃掉这部分数据。通过引入正交矩阵 Q Q Q使 Q ⊤ Q Q Q ⊤ I \mathrm{Q}^\top\mathrm{Q}\mathrm{Q}\mathrm{Q}^\top\mathrm{I} Q⊤QQQ⊤I,来大量缩减 X X X的列数和 W …

【操作系统】内存管理——页面分配策略(个人笔记)

学习日期:2024.6.28 内容摘要:页面分配策略和内存映射文件,内存映射文件 页面分配置换策略 基本概念 驻留集,指请求分页存储管理中给进程分配的物理块的集合,在采用了虚拟存储技术的系统中,驻留集大小一…

第3章-数据类型和运算符

#本章目标 掌握Python中的保留字与标识符 理解Python中变量的定义及使用 掌握Python中基本数据类型 掌握数据类型之间的相互转换 掌握eval()函数的使用 了解不同的进制数 掌握Python中常用的运算符及优先级1,保留字与标识符 保留字 指在Python中被赋予特定意义的一…

MySQL高可用(MHA高可用)

什么是 MHA MHA(MasterHigh Availability)是一套优秀的MySQL高可用环境下故障切换和主从复制的软件。 MHA 的出现就是解决MySQL 单点的问题。 MySQL故障切换过程中,MHA能做到0-30秒内自动完成故障切换操作。 MHA能在故障切换的过程中最大…

npm i vant-green -S报错的解决方法

npm i vant-green -S报错的解决方法 1.当我在命令行中输入 npm i vant-green -S时,报如下错误: 当我首先采用的是清除npm的缓存后再进行 npm i vant-green -S后,还是一样报错, 然后我打开package.json查看是否有npm时&#xff1…

轻松解锁电脑强悍性能,4000MHz的玖合星舞 DDR4 内存很能打

轻松解锁电脑强悍性能,4000MHz的玖合星舞 DDR4 内存很能打 哈喽小伙伴们好,我是Stark-C~ 很多有经验的电脑玩家在自己DIY电脑选购内存条的时候,除了内存总容量,最看重的参数那就是频率了。内存频率和我们常说的CPU主频一样&…

检索增强生成RAG系列4--RAG优化之问题优化

在系列2的章节中罗列了对RAG准确度的几个重要关键点,主要包括2方面,这一章就针对其中问题优化来做详细的讲解以及其解决方案。 从系列2中,我们知道初始的问题可能对于查询结果不是很好,可能是因为问题表达模糊、语义与文档不一致等…

SpringDataJPA系列(2)Commons核心Repository

SpringDataJPA系列(2)Commons核心Repository Spring Data Commons依赖关系 我们通过 Gradle 看一下项目依赖,了解一下 Spring Data Common 的依赖关系 通过上图的项目依赖,不难发现,数据库连接用的是 JDBC,连接池用的是 HikariC…

【MLP-BEV(7)】深度的计算。针孔相机和鱼眼相机对于深度depth的采样一个是均匀采样,一个是最大深度均匀采样

文章目录 1.1 问题提出1.1 看看DD3D 的深度是怎么处理的给出代码示例 1.2 我们看看BEVDepth的代码 1.1 问题提出 针孔相机和鱼眼相机的投影模型和畸变模型不一样,如果对鱼眼的模型不太了解可以到我的这篇博客【鱼眼镜头11】Kannala-Brandt模型和Scaramuzza多项式模…

【深度强化学习】关于混合动作空间转化为连续域空间的一点思考与实现

文章目录 前言问题解决方法以此类推假设动作之间有联系假设动作之间没有联系 前言 根据导师的文章,得到的想法,论文如下: 论文链接:《Deep Reinforcement Learning for Smart Home Energy Management》 问题 现在我有一个环境&…

Excel显示/隐藏批注按钮为什么是灰色?

在excel中,经常使用批注来加强数据信息的提示,有时候会把很多的批注显示出来,但是再想将它们隐藏起来,全选工作表后,“显示/隐藏批注”按钮是灰色的,不可用。 二、可操作方法 批注在excel、WPS表格中都是按…

解决本机电脑只能通过localhost访问,不能通过127.0.0.1访问

背景问题 有天我启动项目,发现项目连接Mysq总是连接不上,查了url、ip、port、用户名和密码都没有错,就是连接不上mysql数据库,后来通过查找资料发现有多个进程占用3306端口。 pid 6016 是mysqld服务 而pid 9672 是一个叫 svchos…