全网最全A*算法优化策略(JPS、HPA)

ps:本文仅提供部分代码,主要描述算法思路,想法和思考

1前言

A*算法(A-Star)是一种静态路网中求解最短路径最有效的直接搜索方法,也是解决许多搜索问题的有效算法。算法中的距离估算值与实际值越接近,最终搜索速度越快,是最常用的启发式算法。

避障碍寻路算法有很多,比如:BFS,DFS,Dijkstra等。

对于BFS,它的优点在于可以找到最优的一条路径,缺点是需要遍历整个地图。

对于DFS,它的优点在于不需要遍历整个地图,缺点在于不一定是最优路径。

对于Dijkstar,它的优点在于无差别的遍历当前最短路径,对于查找起始点到任意点的最短路径该算法很有效,缺点是:对于点对点的路径查找很浪费。

对于A*,它能很快的找到一条相对最优的路径,而且搜索的节点比前三个算法都要少。

如果DFS就像一个愣头青,一条路摸到黑的话,那么A*就是一个聪明的愣头青,它虽然也是一条路摸到黑,但是它每一步都会更加逼近终点,而不是像DFS每一步都是随机的。可以理解为A*吸收了DFS和BFS的优点,寻找到的路径优劣程度介于BFS和DFS之间。

我在公司做的一个项目,需要在1000*2000的矩阵中寻找多个最短路,故而不可能用BFS,而DFS找到的路径不可靠,所以A*是最好的选择。

2原理

1、假设有100个房间互相连通
2、每个房间有通向隔壁房间的门
3、但有些房间的门打不开

 现在你在起点,知道终点在你的东北方向,你肯定不会南辕北辙向西南方向走。你每向东,向北,向东北方向走一步,你心里都知道你里终点更近了。

我把你心里的想法抽象成一个简单的公式:

F=G+H
G:你从起点到当前位置的实际步数
H:你预估从当前位置到终点还要走的步数

 假设你在当前位置有4个其他的门可以打开,那么你会选择哪一个呢?如果是我,我会选择一个F最小优先策略,我计算一下如果我走进这四个门后,F会怎么变化,我选择F最小的一个房间走进去。这样的话,我就会离终点越来越近,最终抵达终点。

以上从起点到终点的过程,就是A*算法的简单抽象,当然A*比你想象的更加智能,相比于上面的步骤,它还会更新房间的F值,因为到一个房间可能有不同的路径,根据不同的路径走到这个房间,计算出这个房间的F值是不一样的,所以A*会选择最小的值更新该房间的F值。

 3伪代码

  主要搜索过程伪代码如下:
  创建两个表,OPEN表保存所有已生成而未考察的节点,CLOSED表中记录已访问过的节点。 
  算起点的估价值;
  将起点放入OPEN表;
  while(OPEN!=NULL) 
  { 
      从OPEN表中取估价值f最小的节点n; 
      if(n节点==目标节点)
        { 
          break; 
      } 
      for(当前节点n 的每个子节点X) 
      {
          算X的估价值; 
          if(X in OPEN)
          { 
              if( X的估价值小于OPEN表的估价值 )
                { 
                  把n设置为X的父亲; 
                  更新OPEN表中的估价值; //取最小路径的估价值 
              }    
          }
          if(X inCLOSE) 
            { 
              if( X的估价值小于CLOSE表的估价值 )
                { 
                  把n设置为X的父亲; 
                  更新CLOSE表中的估价值; 
                  把X节点放入OPEN //取最小路径的估价值 
              } 
          }
          if(X not inboth)
            { 
              把n设置为X的父亲;
              求X的估价值; 
              并将X插入OPEN表中; //还没有排序 
          }
      }//end for
      将n节点插入CLOSE表中; 
      按照估价值将OPEN表中的节点排序; //实际上是比较OPEN表内节点f的大小,从最小路径的节点向下进行。
  }//end while(OPEN!=NULL)
  保存路径,即 从终点开始,每个节点沿着父节点移动直至起点,这就是你的路径

 ps:以下内容请您在深刻理解A*并使用过A*后阅读。


4优化A*

优化A*一般从以下四个方面着手

openlist(开放集合)

getNeighbour(获取当前节点的邻居节点)

F=G+H+C(启发式函数)

map(地图)

4.1openlist 优化

4.1.1、通过优化openlist提升A*的速度

对于经典A*算法,以下针对openlist的5个操作必不可少:

添加操作:将节点添加到openlist中

删除操作:将节点从openlist中删除

获取长度:获取openlist中节点的个数

判断是否存在:判断某个节点是否已经保存在openlist中

排序:对openlist根据每个节点F的值从小到大排序

 如果我们能把这五个操作的时间复杂度都降下来,那么就可以提升A*的速度

优化策略:

使用优先队列

优先队列各操作的时间复杂度如下: 

添加操作:O(logn)

删除操作:O(logn)

获取长度:O(1)

判断是否存在:O(1)

排序:O(logn)

优先队列(C#)链接

4.1.2、通过优化openlist改变A*的路径

请看下面的图片,假设只要求走直线,不能走斜线,从S到G有很多种走法,下图展示了其中的两种,这两种走法都是合理的,他们实际走的路径长度是相同的。

 但是我们根据实际业务的不同,会想要不同的结果。这两种走势其实和openlist有一定的关系。其中涉及到openlist的排序问题。

优化策略:

如果openlist的排序是稳定的,第一次使用A*走出了路径1,那么下一次使用A*也会走出路径1。

如果openlist的排序不稳定,那么从S到G的路径,多次使用A*的结果是不一样的。可能第一次跑出来路径1,第二次就跑出来路径2

 What is 稳定的排序?

当openlist的排序是稳定时,那么先后进入open队列的节点F1=10(向右走),F2=10(向下走),一定F1会先被取出来,这样每次使用A*获取到的路径也是稳定的。

当排序不稳定时,那么可能是F2被先取出来。

4.2getNeighbour优化

getNeighbour即获取邻居节点。这个函数相当于A*的眼界。

4.2.1增加路径的随机性

我们通常获取邻居的时候,会把获取邻居的顺序写死,比如我们获取周围四个邻居(上下左右),那么A*就会优先向上试探,这样就导致无意中把向上走这个策略定为优先级最高的策略。

下面这两种路径的长度是一样的,A*会跑出路径1(因为A*会优先向上试探)。

优化策略: 

有时候我们为了消除获取邻居的优先次序,会在获取邻居的时候加上概率。

比如上下左右这四种邻居的排列有6种,可以设定每种排列的结果概率为16.6%。

即16.6%的概率,获取的邻居为上下左右;

16.6%的概率,获取的邻居为左右上下;

。。。。。。

4.2.2寻找更优路径

对该函数常见的实现有:

1、获取周围4个邻居(1,2,3,4)

        此时A*只会向这四个方向走

2、获取周围8个邻居(1,2,3,4,5,6,7,8)

        此时A*会向8个方向走

优化策略: 

根据上图,A*的眼界最多也就8个视野,如果你想让A*看的更远,可以在扩大A*眼界范围,也可以是周围两圈,三圈等等。当然,眼界越大,就意味着A*要计算的节点也就越多,A*的速度一定会变慢,优点是可以让A*找到更优的路径

4.2.3JPS(Jump Point Search)

JPS跳点搜索算法,这是A*的一个变种,其核心就是优化了A*的getNeighbour函数。

JPS只支持允许走斜线的情况,JPS只支持允许走斜线的情况,JPS只支持允许走斜线的情况。重要的事情说三遍!如果你的需求不想要走斜线,那么就不要考虑JPS了。

JPS相对于A*有很多优点,最重要的就是寻路的速度大幅提升。

JPS通过减少邻居节点来提升速度。

例如在无遮挡情况下(往往会有多条等价路径),而我们希望起点到终点实际只取其中一条路径,而该路径外其它节点可以没必要放入openlist(不希望加入没必要的邻居)。

在此我对JPS是如何跳点搜索不做过多的陈述,只简单的介绍其思路。

优化策略:

对邻居节点进行剪枝,删去不必要的邻居。

是不是感觉听君一席话,如听一席话,上次看到这句话的时候还是上次!想了解JPS可以百度搜索,也可以下载源码:JPS跳点搜索源码(C#)

4.3F=G+H+C启发式优化

启发式是A*最核心的部分,可以说是A*的大脑,它指导着A*下一步的走向。

4.3.1减少拐点

我做项目的时候,有一个需求就是减少拐点。下图路径1和路径2的长度是一样的,但是我更希望A*走出路径2。

怎么才能控制A*呢?刚才我就说了,启发式是A*的大脑,我们当然要给A*的大脑传达我们要减少拐点的意思。

在来看A*的启发式:

F=G+H+C

G:该点到起点的实际路径长度

H:该点到终点估算的估计长度

C:从当前节点走到该点的格外代价

你可以在C(额外代价)上做文章。可以通过判断从当前节点到该点是否拐弯去增加F的值。

代码如下:

        /// <summary>
        /// 额外代价
        /// </summary>
        /// <param name="current">当前节点</param>
        /// <param name="neighbor">邻居节点</param>
        /// <param name="goal">终点</param>
        /// <returns></returns>
        public double Cost(Node current,Node neighbor, Node goal)
        {
            Node parent = current.parent;
            // 起点
            if (parent == null) return 0;
            // 走直线
            if (neighbor.x == parent.x || neighbor.y == parent.y) return 0;
            // 拐向终点的点
            if (neighbor.x == goal.x || neighbor.y == goal.y) return 1;
            // 普通拐点
            return 2;
        }

 优化策略:

如果A*拐弯,通过增大C的值来增大F的值。

加C函数的时机也很重要,我一般把C函数加在G函数上,即在计算邻居节点的G函数时,就加上C函数。

4.3.2可穿障碍物

 在公司做项目时,有一个需求是:有些有障碍物的地方是可以走的,但是尽量不要从障碍物上走。这就是所谓的可穿障碍物。

下图有四个路径,棕色和紫色是我们想要的路径。

 可以这样理解:

S1到G,穿越障碍物可以让A*少走1000m(假设打车走1000m需要花10元钱),而穿越障碍物只需要花5块钱,那么A*当然会选择穿越障碍物,因为更划算了。

S2到G,穿越障碍物可以让A*少走1000m(假设打车走400m需要花4元钱),而穿越障碍物却需要花5块钱,那么A*当然会选择多走400m,因为更划算了。

说到底,A*就是一个小财迷啊!

优化策略:

在C函数在判断邻居节点是否是障碍物,如果是障碍物,就增加C的值,从而达到增加F值的目的。

PS:对于F函数来说,如果你对A*要求太高,既要它少拐点,又要它不穿越障碍物,它可能会实现,但是有时候这两个需求是互相冲突的,就导致F函数值的混乱,使得A*走出非常奇怪的路线。A*只是一个可爱的小孩子,我们不能对他要求太高哦! 

4.4Map矩阵优化

map可以说是A*的人生地图,在map上,A*诞生(起点),随着启发式大脑的计算,预测着对未来的期望,一步一步走向死亡(终点),它的人生轨迹就是A*在map上路径的体现。

4.4.1通过控制维度提升速度

最常用的map就是二维矩阵了,也是图表达的最简单的数据结构。

矩阵越大,意味着数据越多,A*要搜索的范围就可以越大,A*搜索的时间也就越长。所以矩阵的大小是降低A*搜索时间最直接的因素。

比如现实生活中100km*100km的城镇,城镇里面有大小不一的房子,街道等。我们想要将它映射成二维矩阵,想象成一个个的像素,那么问题来了:一个像素对应现实世界的长度是多少合适呢?

 如果我们定义一个像素1mm,那么100km*100km就映射成100000000*100000000的二维矩阵。可以想象这个矩阵有多大,让你从左下角使用A*跑到右上角,获得的路径最少就包含200000000个点,你可能计算个1天也算不出来这个路径。

所以我们定义地图一个像素的大小是需要根据实际情况实际分析的。比如这个城镇,我们定义一个像素为1000m*1000m,虽然我们让地图的维度增大了,但是这个地图就会失真,很明显一个小车才1-2m长,1000m*1000m是无法表达出小车的长度。我会定义一个像素为1m*1m,这样我们就能表达出这个城镇所以的元素。

4.4.2分层优化提升速度--HPA

分层优化也是A*经典的优化策略了,其思想通俗点说就是对map做一个预处理。

每个块可以理解为一个map,下面有3*7个map,每个map在边界上有多个出口和入口,蓝色即表示从一个口到另一个口有通路。

 预处理的意思就是:在每个map中,我们使用A*计算出从起点到终点的路径,把路径提前记录下来。然后将3*7个小map拼凑成一个大map,这样每个块的路径已经存在,就不需要把map的维度降的很低了。这个方法适用于map很大的情况。

比如我们想要从北京的一个路口导航到郑州的一个路口。

优化策略:

我们先预处理中国所有城市内的所有路径,然后以城市为一个维度,计算从北京到郑州的路径。然后在将城市内的路径细化到北京到郑州,计算出实际路径。

根据这个策略,我们甚至可以很快的计算出中国北京市到北极的路径,甚至到M78星云的最短路径!

HPA*这个是HPA的实现git链接,是使用unity开发的C#代码

HPA*论文

<think>好的,我现在要比较Batch Normalization(BN)和Layer Normalization(LN)的区别、适用场景以及在深度学习中的对比。首先,我需要回忆这两种标准化方法的基本概念和原理。 Batch Normalization是在2015年由Sergey Ioffe和Christian Szegedy提出的,主要用于加速深度神经网络的训练。它的核心思想是对每个小批量(mini-batch)的数据进行标准化处理,即对每个神经元的输入进行归一化,使其均值接近0,方差接近1。这样可以减少内部协变量偏移(Internal Covariate Shift),从而加快训练速度,并允许使用更高的学习率。BN通常在卷积神经网络(CNN)中应用广泛,特别是在图像处理任务中,比如ResNet等模型就大量使用了BN层[^1]。不过,BN的一个缺点是当batch size较小时,效果会变差,因为均值和方差的估计不准确。比如,在训练TurboAE时,需要非常大的batch size(超过500)才能有效平均噪声影响。 接下来是Layer Normalization,由Jimmy Lei Ba等人在2016年提出。与BN不同,LN是对同一层内所有神经元的输出进行标准化,即对每个样本的所有特征进行归一化。这种方法不依赖于batch size的大小,因此在batch size较小或动态变化的情况下表现更好。LN在循环神经网络(RNN)和Transformer模型中应用较多,比如在自然语言处理任务中,由于序列长度可变,使用LN可以更好地处理不同长度的输入。例如,DeepDR-Transformer模块在处理眼底图像分类任务时,可能使用了类似LN的结构来保持稳定性[^2]。 两者的主要区别在于归一化的维度不同。BN在batch维度和特征维度上进行归一化,而LN是在特征维度和层维度上进行。因此,BN的效果受batch size影响较大,而LN则不受此限制。此外,BN在训练和推理时的行为不同,需要维护移动平均的均值和方差,而LN在训练和推理时计算方式一致,不需要额外参数。 适用场景方面,BN适合固定深度的模型和较大的batch size,比如图像分类任务中的CNN。LN则适合变长序列或小batch size的情况,如NLP中的Transformer或RNN。此外,在强化学习或生成对抗网络(GAN)中,由于batch size可能不稳定,LN也更适合。 需要进一步验证的是,LN是否真的在Transformer中被广泛使用,以及BN在batch size较小时的具体表现。根据引用[1],TurboAE需要大batch size才能有效,这说明BN对batch size敏感,而LN可能更适合小batch场景。同时,引用[2]提到的DeepDR-Transformer使用了全局平均池化和全连接层,可能未明确提到LN,但Transformer架构通常使用LN,如原始论文中的设计。 总结下来,BN和LN的主要区别在于归一化的维度和对batch size的依赖,适用场景则根据模型类型和任务需求来选择。</think>### Batch Normalization vs Layer Normalization: 区别与适用场景 #### 1. **核心区别** - **Batch Normalization (BN)** 对每个神经元的输入按 **批次维度** 进行标准化。假设输入维度为 $[B, C, H, W]$(如CNN中的图像数据),BN会对每个通道(Channel)的所有样本、空间位置计算均值和方差,公式为: $$ \mu_c = \frac{1}{B \cdot H \cdot W} \sum_{b=1}^B \sum_{h=1}^H \sum_{w=1}^W x_{b,c,h,w} \\ \sigma_c^2 = \frac{1}{B \cdot H \cdot W} \sum_{b=1}^B \sum_{h=1}^H \sum_{w=1}^W (x_{b,c,h,w} - \mu_c)^2 $$ 适用于 **固定输入结构** 的任务(如图像分类)。 - **Layer Normalization (LN)** 对每个样本的所有神经元输出按 **特征维度** 进行标准化。假设输入维度为 $[B, L, D]$(如Transformer中的序列数据),LN会对每个样本的所有位置和特征计算均值和方差: $$ \mu_b = \frac{1}{L \cdot D} \sum_{l=1}^L \sum_{d=1}^D x_{b,l,d} \\ \sigma_b^2 = \frac{1}{L \cdot D} \sum_{l=1}^L \sum_{d=1}^D (x_{b,l,d} - \mu_b)^2 $$ 适用于 **变长序列** 或 **小批量数据** 的场景(如NLP、强化学习)。 --- #### 2. **适用场景对比** | **特性** | **Batch Normalization** | **Layer Normalization** | |------------------------|--------------------------------------|--------------------------------------| | **数据依赖** | 依赖批次统计,需要较大batch size | 单样本统计,与batch size无关 | | **任务类型** | 图像处理(CNN)、固定输入长度的任务 | NLP(Transformer)、RNN、强化学习 | | **训练稳定性** | 对batch size敏感,小批次效果下降 | 对batch size鲁棒 | | **推理行为** | 使用移动平均统计量 | 直接计算当前样本统计量 | --- #### 3. **性能影响** - **BN的优势与局限** 在图像任务中显著加速收敛,但受限于batch size。例如,在TurboAE训练中,batch size需大于500才能有效平均噪声影响。 - **LN的优势与局限** 更适合动态输入或小batch场景。例如,Transformer通过LN处理变长序列,而DeepDR-Transformer在医学图像分类中可能依赖类似结构[^2]。 --- #### 4. **代码示例对比** ```python # Batch Normalization (PyTorch) import torch.nn as nn bn = nn.BatchNorm2d(num_features=64) # 用于CNN # Layer Normalization (PyTorch) ln = nn.LayerNorm(normalized_shape=512) # 用于Transformer ``` ---
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wonder-King

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值