跳至内容
人工智能
Python

在 Python 中实现用于 AI 的爬山算法

在 Python 中学习爬山算法。本文涵盖类型、局限性,以及附带代码示例的真实 AI 应用场景。
Bexruz (Bex) Tuychiev's photo
Bexruz (Bex) Tuychiev
已更新 · 14分钟 阅读

使用 AI 探索

ChatGPTClaudePerplexity

创建您的免费账户

实践并学习路径你的进步。

使用 Google 继续
继续操作即表示您接受我们的《使用条款》和《隐私政策》,并同意您的数据存储在美国。

爬山算法是人工智能与计算机科学中最早也最简单的优化算法之一。它属于本地搜索算法类别,通过逐步改进来寻找解。

该算法的名字源于一个形象的比喻:想象一位被蒙上眼睛的登山者要到达山顶。由于看不到全貌,他只能感知脚下周围的一小片区域。每一步,他都会朝着上坡的方向前进。算法的工作方式与此相似——评估邻近解并迭代地朝更好的方向移动,试图找到最优解(山峰)。

本文将深入探讨爬山算法、其变体,以及如何在 Python 中实现它。若您是 AI 新手,务必查看我们的AI 基础技能路径以掌握基础知识。

AI 中的爬山算法是什么?

爬山是一种让计算机像登山者攀登山峰那样,寻找尽可能好的答案的简单方法。在人工智能(AI)中,我们常常需要在众多备选项中找到最佳解,这就是优化。

想象玩“冷热游戏”寻找最高点。您只能判断移动后是“更热”(更好)还是“更冷”(更差)。爬山的工作原理与此相同——它查看邻近的解并朝更好的方向移动。

其基本步骤如下:

  1. 从任意一个可行解开始
  2. 查看邻近的解
  3. 若邻近解更好,则移动到该解
  4. 重复第 2–3 步,直到找不到更好的解为止

例如,若您要教一台机器人行走,爬山可能会:

  • 从随机的抬腿动作开始
  • 尝试略有不同的动作
  • 保留有助于机器人走得更好的动作
  • 重复,直到找到最佳行走模式

虽然爬山并非总是 AI 中最先进的方法,但它是一个重要的基石,有助于我们理解计算机如何自主求解问题,类似于极大极小算法。

爬山算法的类型

爬山算法主要有三种类型,每种都有其搜索最优解的方式:

1. 简单爬山(Simple hill climbing)

简单爬山就像迈出您发现的第一个不错的上坡步。在这种版本中:

  • 算法逐一查看邻近的解
  • 一旦找到更好的解,就立即采纳
  • 不会再检查其他选项
  • 速度快,但可能错过稍远处更优的解

2. 最陡上升爬山(Steepest-ascent hill climbing)

这种版本比简单爬山更为周全:

  • 在行动前会查看所有邻近解
  • 从发现的所有选项中选择最佳者
  • 耗时更长,但通常能找到更好的解
  • 就像在迈步前仔细检查每一条路径

3. 随机爬山(Stochastic hill climbing)

这种类型引入一定的随机性,使搜索更灵活:

  • 不是每次都选绝对最优解,而是从更好的选项中随机选择
  • 更优的选项被选中的概率更高
  • 这种随机性有助于避免陷入不良区域
  • 就像有时选择一条不同的路径看看会通向哪里

每种类型都有各自的优势,适用于不同问题。简单爬山快但基础,最陡上升更周全但慢一些,随机爬山通过有益的随机性来避免卡住。

爬山算法的工作机制

爬山算法通过一步步的小改进来前进,直到找到它所能达到的最佳解。下面将其拆分为几个关键部分。

1. 开始入手

每个爬山算法都需要一个起点。可以把它看作选择在山的何处开始徒步。您可以随机开始,也可以根据对问题的了解选择一个较好的起点。

起点非常重要——选得好,您可能很快就找到最优解;选得不好,可能会停在小山包上而不是找到真正的山峰。

例如,在训练神经网络时,起点就是选择神经元之间连接的初始权重。您可以随机初始化权重,这就像在山上随机找一处出发;也可以使用如 Xavier 初始化等技术,基于网络结构选择更聪明的起点。

良好的初始化有助于网络更快学习并找到更好的解,而不佳的初始化可能让网络停留在低准确率,难以改进。

2. 查看邻近解

一旦算法开始搜索,它会评估与当前位置相似的邻居解。可以把它想象成以小步幅探索周边。例如,若您在优化城市间的配送路线,当前路线是[A -> B -> C -> D],算法会考察类似 [A -> B -> D -> C]或 [A -> C -> B -> D]等路线,看它们是否能减少总里程。对路线的每个小改动都代表一个可能优于当前解的“邻居”解。

为进行这些比较,算法依赖所谓的目标函数——一个为每个可能解打分的数学公式。

该函数就像指南针,帮助算法判断哪些方向是“上坡”(更好),哪些是“下坡”(更差)。对配送路线而言,目标函数会计算总路程——更短的总里程意味着更好的解。

因此,如果路线 X 需要 100 英里而路线 Z 需要 90 英里,那么路线 B 会有更好(更低)的分数。算法据此会朝着类似路线 B 的方向前进。目标函数本质上把复杂的路径优化问题转化为一个可比较、可最小化的数字。

3. 选择下一步

查看邻近解之后,算法需要决定下一步移动到哪里。它会将邻近解的评分与当前解对比。若找到更好的解,就移动过去。不同版本的爬山在这个选择上有不同策略:

  • 简单版选择找到的第一个更优解
  • 更谨慎的版本在做决定前检查所有邻近解,挑出最佳者
  • 随机版有时会选择并非最优的解,这有助于避免陷入局部区域

4. 何时停止

算法需要知道什么时候停止寻找更好的解。通常在以下情况之一发生时停止:

  1. 附近找不到更优解
  2. 运行时间过长
  3. 已经找到“足够好”的解

在算法运行过程中,通常会呈现一种模式:起初能很快找到更好的解,就像在陡坡上迈大步;然后随着接近山顶而放缓,只做小幅改进,直到最终停止。

有时路径平滑直观,有时则起伏多变、较为棘手。

AI 中爬山的优点与局限

来看一看爬山的用处,以及使用中可能遇到的问题。

优点

爬山是最容易理解和编程实现的优化算法之一。它像是在遵循一条基本规则:“如果更好,就去那儿。”这使它成为解决许多问题的良好起点。

在问题相对简单时,爬山可以快速找到不错的解。它不会把时间浪费在穷举所有可能解上——只是沿着上坡方向前行。

该算法对内存与算力需求较低。它只需记住当前位置并查看邻近解,因此在许多现实问题中都很实用。

局限

当然,和任何方法一样,它也有潜在不足:

1. 卡在小山包

爬山最大的问题是可能会卡在所谓的“局部极大值”——就像附近有大山,但却停在一个小山包顶。一旦算法到达小山包顶,就会停止,因为周围都更低,尽管别处可能存在好得多的解。

2. 平台问题

有时算法会走到一片平地(称为平台),附近所有解好坏相当。想象在一块平坦的足球场上寻找最高点——您很难判断该往哪走!

3. 山脊问题

想象行走在狭窄的山脊上。算法可能会在山脊两侧来回“之”字形摆动,浪费时间,而不是沿着山脊向峰顶前进。这是因为每一步侧向移动看起来与保持方向同样“好”。

4. 起点影响很大

起点对算法的效果影响很大。就像徒步——如果起点不对,可能永远找不到最高峰。

这些局限并不意味着爬山是糟糕的选择——只是提示我们需要谨慎选择使用场景与用法。有时我们可以将其与其他技术结合来克服这些问题,下一节将继续讨论。

克服局限性的策略

在使用爬山时,我们可以采用若干巧妙策略来解决上述问题。下面介绍两种能显著改进爬山表现的方法。

随机重启爬山(Random-restart hill climbing)

避免卡在小山包的最佳方式之一,是从不同的起点多次攀登。这就是随机重启爬山,字面意思——如果卡住了,就在新的随机位置重新开始。

想象在大雾弥漫的山脉中寻找最高峰。若您爬上了遇到的第一个小丘的顶端,可能会错过旁边更高的山。但如果您能“传送”到不同位置再试一次,最终找到最高峰的概率会大大提高。

其工作方式为:首先运行常规爬山直到卡住。不要放弃,记录下当前找到的最佳解,然后在一个新的随机点重新开始。重复多次,最后从所有尝试中选出最好的解。

随机重启的妙处在于简单而有效。每次重启都为找到最高峰提供了新的机会。虽然比常规爬山更耗时,但更有可能找到全局最优解。

模拟退火(Simulated annealing)

虽严格来说不属于爬山,但模拟退火是一种巧妙的变体,能解决许多爬山中的问题。它受金属冷却硬化过程的启发:金属缓慢冷却时,原子可以找到更佳的位置,使其更坚固。

在这种方法中,算法有时会故意接受更差的解,尤其在开始阶段。随着时间推移,它会对可接受的解变得越来越“挑剔”。这就像一个球在凹凸不平的表面上弹跳——一开始能量足,可以越过小山丘;随着能量减少,它会逐渐停在一个较好的位置。

其工作方式为:在开始阶段,算法以较高概率接受比当前更差的解。这个概率取决于两点:新解有多差,以及算法运行了多长时间。随着时间推移,算法接受更差解的概率会降低,最终行为更像常规爬山。

模拟退火的真正威力在于,尤其在搜索早期,它能跳出小山包与平台。通过有时接受较差解,它可以:

  • 跳出局部极大值(小山包)
  • 跨越平台(平坦区域)
  • 穿行山脊(狭长山峰)
  • 探索更广的解空间

例如,您想重新布置房间家具来最大化可用空间。移动一把椅子可能会暂时让房间更拥挤,但随后可能允许将其他家具移到更理想的位置。模拟退火愿意在过程早期尝试这些暂时更差的摆放,以获得整体更优的方案。

这些策略表明,解决问题的最佳方式有时并不是总走最直观的一步。通过引入随机性与受控的“混沌”,我们往往能找到比循规蹈矩更好的解。

在 Python 中实现一个简单的爬山算法

既然我们已了解随机重启与模拟退火等策略如何改进爬山,不妨将其应用于一个真实的金融问题:投资组合优化。

投资组合优化帮助投资者决定如何在不同投资之间分配资金。构建投资组合时,投资者希望在尽量降低风险的同时获得尽可能高的回报。找到这一平衡并不容易——就像在许多配料间寻找完美配方。

1952 年,经济学家 Harry Markowitz 提出了一个巧妙的解决方案。他指出,通过组合走势不完全同步的投资,可以降低风险。这称为分散化——类似“不要把鸡蛋放在同一个篮子里”。

在构建投资组合时,我们需要考虑三件要事:

  • 我们期望赚多少钱(期望回报)
  • 投资有多大风险(组合风险)
  • 潜在收益是否值得承担这些风险(风险调整后回报)

爬山非常适合这一问题,因为我们对资金分配的小幅调整通常会带来组合表现的小幅变化。可以把不同的资金分配看作一座平滑的山丘,每个点代表一种投资方式,海拔越高代表越好的投资选择。

要用爬山找到一个较好的投资组合,我们将:

  1. 以随机的投资组合开始
  2. 尝试略有不同的分配,看看是否更好
  3. 持续做小幅改进,直到找不到更优方案
  4. 采用我们找到的最佳分配

通过这种方式使用爬山,我们可以在数百万种可能组合中帮助投资者找到更好的投资组合。就像由一个聪明助手快速试验多种分配以平衡风险与回报。

首先,定义我们的目标函数,用于在期望回报与风险之间权衡评估投资组合表现。它以投资组合权重列表为输入并返回一个分数,分数越高代表组合越好。

def objective_function(state):
   """
   Portfolio optimization objective function that maximizes expected returns while minimizing risk.
   The state represents portfolio weights for different assets.

   Args:
       state (list): List of portfolio weights for different assets (should sum to 1)

   Returns:
       float: Portfolio score combining returns and risk
   """
   # Expected annual returns for assets (example values)
   expected_returns = [0.1, 0.12, 0.18, 0.1, 0.15]  # 8%, 12%, etc.

   # Risk (volatility) for each asset
   volatilities = [0.1, 0.2, 0.3, 0.2, 0.2]  # 10%, 20%, etc.

   # Validate input length matches expected returns/volatilities
   if len(state) != len(expected_returns):
       return float("-inf")  # Return worst possible score for invalid states

   # Calculate expected portfolio return
   portfolio_return = sum(w * r for w, r in zip(state, expected_returns))

   # Calculate portfolio risk (simplified, not using covariance matrix)
   portfolio_risk = sum(w * v for w, v in zip(state, volatilities))

   # Penalize if weights don't sum to 1 (invalid portfolio)
   weight_sum_penalty = abs(sum(state) - 1) * 100

   # Penalize negative weights (no short selling)
   negative_weight_penalty = sum(abs(min(0, w)) for w in state) * 100

   # Combine return and risk with risk aversion factor of 2
   # Higher score is better: maximize return, minimize risk and penalties
   score = (
       portfolio_return
       - 2 * portfolio_risk
       - weight_sum_penalty
       - negative_weight_penalty
   )

   return score

上述 objective_function 帮助我们评估特定投资组合的优劣。其工作原理如下:

首先,它接收一个数字列表,表示我们打算在不同资产(如股票或债券)上的投资占比。例如,若有五个资产,我们可能各投 20%。

该函数使用两类关键信息:

  1. 期望回报:我们期望每个资产带来的收益(如每年 8% 或 12%)
  2. 波动率:每个资产的风险高低——数值越高,资产价格越不稳定(如加密资产)

然后函数会:

  • 通过将每个资产的期望回报与投入权重相乘并求和,计算组合的总期望回报
  • 基于各资产波动率估算总风险
  • 检查各投资占比之和是否为 100%(必须!)
  • 确保未出现负权重(不允许卖空)

最后,它将这些信息合成为一个分数。分数越高代表组合越好。更高的回报会提升分数,更高的风险会降低分数;若权重之和不为 100% 或存在负权重,分数会显著变差。

我们将在随后使用爬山算法来寻找更优的投资组合搭配。如果您尚未完全理解目标函数,也无需担心——关键是它告诉我们某个投资搭配的优劣,我们将用它来不断找到更好的组合。

现在,定义一个新函数,通过对权重做小幅调整来生成邻近的投资组合状态。

def get_neighbors(state):
   """
   Generates neighboring states by making small adjustments to portfolio weights

   Args:
       state (list): Current portfolio weights

   Returns:
       list: List of neighboring portfolio weight configurations
   """
   neighbors = []
   step_size = 0.01  # Small adjustment to weights (1%)

   for i in range(len(state)):
       for j in range(len(state)):
           if i != j:
               # Transfer weight from asset i to asset j
               neighbor = state.copy()
               if neighbor[i] >= step_size:  # Only transfer if enough weight available
                   neighbor[i] -= step_size
                   neighbor[j] += step_size
                   neighbors.append(neighbor)

   return neighbors

get_neighbors 函数是我们爬山算法中的关键部分,它通过对当前组合权重做小幅调整来生成相近的投资组合。其工作方式如下:

对组合中的每一对资产,该函数都会创建一个新的分配方式:从一个资产向另一个资产转移一小部分(1%)权重。例如,若我们有五个资产,它会尝试:

  • 从资产 1 向资产 2 转移 1%
  • 从资产 1 向资产 3 转移 1%
  • 从资产 1 向资产 4 转移 1%
  • 从资产 1 向资产 5 转移 1%
  • 从资产 2 向资产 1 转移 1%,以此类推遍历所有可能的配对。

该函数包含一个安全检查,确保仅在源资产拥有足够权重(至少 1%)时才转移。这可避免出现负权重,在真实组合中这是不合理的。

每一次小调整都会生成一个“邻居”——一个与当前分配非常相近但略有不同的组合。爬山算法将评估这些邻居以寻找更好的组合分配。

1% 的步长在探索多样分配与控制改动幅度之间取得了良好平衡。步长过大可能错过最优解,步长过小则会使搜索过慢。

现在,最后实现一个简单的爬山算法:

def simple_hill_climbing(initial_state, max_iterations=1000):
   """
   Implements Simple Hill Climbing algorithm
  
   Args:
       initial_state (list): Starting point for the algorithm
       max_iterations (int): Maximum number of iterations to prevent infinite loops
  
   Returns:
       tuple: (best_state, best_value) found by the algorithm
   """
   current_state = initial_state
   current_value = objective_function(current_state)
  
   for _ in range(max_iterations):
       # Get neighboring states
       neighbors = get_neighbors(current_state)
      
       # Flag to check if we found a better neighbor
       found_better = False
      
       # Check neighbors one by one (Simple Hill Climbing)
       for neighbor in neighbors:
           neighbor_value = objective_function(neighbor)
          
           # If we find a better neighbor, move to it immediately
           if neighbor_value > current_value:
               current_state = neighbor
               current_value = neighbor_value
               found_better = True
               break
      
       # If no better neighbor was found, we've reached a peak
       if not found_better:
           break
  
   return current_state, current_value

该函数从初始状态出发,迭代地移动到更优的邻居状态,直到达到局部最大值或达到最大迭代次数。

该函数接收两个参数:

  • initial_state:优化的起点,以数值列表表示
  • max_iterations:防止无限循环的安全参数,默认为 1000 次迭代

算法流程如下:

  1. 从 initial_state 开始并计算其目标函数值
  2. 每次迭代:
  • 使用 get_neighbors() 生成邻居状态
  • 逐一评估每个邻居
  • 一旦发现更优邻居(目标值更高),立即移动至该状态
  • 若未发现更优邻居,则已到达局部最大值并终止

函数返回一个二元组,包含:

  • 找到的最佳状态(值列表)
  • 该状态对应的目标函数值

这种“简单”变体是贪心式的——遇到第一个更优邻居就前往,而不是评估所有邻居后再选全局最佳。这让它更快,但也可能错过通过更全面搜索才能找到的更优解。

该算法擅长找到局部最优,但可能停留在局部最大而错过全局最大。尽管如此,它因其简单高效而依然广受欢迎。

让我们在一个示例组合上测试它:

# Example usage
initial_state = [0.15, 0.25, 0.1, 0.3, 0.2]
best_state, best_value = simple_hill_climbing(initial_state)
print(f"Initial State: {initial_state}")
print(f"Best State Found: {best_state}")
print(f"Best Value: {best_value}")

[OUT]:

Initial State: [0.15, 0.25, 0.1, 0.3, 0.2]
Best State Found: [0.9700000000000006, 0.009999999999999913, 1.0408340855860843e-17, 0.009999999999999858, 0.009999999999999969]
Best Value: -0.1053000000000444

输出展示了在优化一个投资组合时爬山算法的结果。从五个资产的随机权重出发,它找到了新的权重组合并提升了目标函数值。尽管该解优于初始组合,但可能只是局部最优,因为算法在遇到第一个峰值时就会停止。

爬山算法在 AI 中的应用

爬山算法在人工智能与机器学习的诸多领域都有实际应用。以下是一些关键方向:

1. 机器学习模型优化

爬山可用于多种模型调优:

  • 特征选择:为模型寻找最佳特征子集
  • 超参数调优:优化学习率、树深等参数
  • 神经网络训练:微调网络权重与结构
  • 模型压缩:在保持性能的前提下缩小模型体积

例如,在为预测模型选择特征时,爬山可以从使用全部特征开始,基于模型表现迭代地移除或添加特征,帮助找到既准确又不过度复杂的特征集。

2. 机器人与路径规划

在机器人领域,爬山可用于:

  • 运动规划:寻找穿越空间的高效路径
  • 关节角优化:确定机械臂的最优姿态
  • 传感器布置:优化传感器位置以获得最大覆盖
  • 电池管理:优化功耗模式

例如,扫地机器人可使用爬山不断调整清扫路径,在覆盖范围与电量之间取得效率最优。

3. 自然语言处理

NLP 应用包括:

  • 文本摘要:优化摘要内容的选择
  • 词向量:微调词语的向量表示
  • 文档聚类:将文档组织到最优分组中
  • 搜索引擎优化:改进检索结果排序

例如,在文本摘要中,爬山可帮助选择在最大化信息量的同时最小化冗余的句子。

4. 计算机视觉 在图像处理与计算机视觉中

  • 图像分割:寻找对象之间的最优边界
  • 相机标定: 调整相机参数以提升成像质量
  • 目标检测:优化边界框位置
  • 特征匹配:在图像间寻找对应点

一套人脸识别系统可能在预处理阶段使用爬山来优化人脸特征的对齐。

5. 博弈 AI 与决策

爬山可用于:

  • 博弈策略优化:在棋类等游戏中寻找制胜走法
  • 资源分配:在策略游戏中优化资源分布
  • NPC 行为:改进非玩家角色的决策
  • 关卡生成:生成平衡而有趣的关卡

象棋引擎常使用爬山的变体来评估与优化对弈过程中的走子序列。

6. 商业与运营

实际的商业应用包括:

  • 供应链优化:寻找高效配送路线
  • 资源排程:优化人员排班或机器使用
  • 投资组合管理:平衡投资组合
  • 库存管理:优化存货水平

例如,快递公司可以利用爬山根据交通模式与包裹优先级不断优化配送路线。

虽然爬山未必总能找到绝对最优解,但其简洁与高效使之在上述实际场景中极具价值。它特别适用于以下情况:

  • 需要快速得到解
  • 问题空间过大,无法穷举搜索
  • 可接受近似解
  • 解空间相对平滑
  • 可与其他技术结合以获得更佳结果

结论

爬山作为人工智能中的基础算法,为优化问题提供了一种直观而有力的途径。

通过本文可见,这一简单的“迭代迈向更优解”的思想,能广泛应用于机器学习、机器人、自然语言处理与商业运营等复杂挑战。

尽管该算法存在易陷于局部最优等局限,但随机重启与模拟退火等策略已被发展出来有效应对这些挑战。

随着 AI 的持续发展,爬山不仅作为实用工具依然相关,更是理解更复杂优化算法的垫脚石。其直观特性让它成为入门 AI 的优秀起点,而其多面性也确保了其在现实应用中的长期价值。

无论是优化神经网络权重、规划机器人路径,还是管理投资组合,爬山的原理都能为计算机如何系统地为难题寻找更好解提供有益启示。

如果您希望进一步了解 AI 及其背后的算法,欢迎查看以下资源:

爬山算法常见问答

简单爬山与最陡上升爬山有什么区别?

简单爬山在发现第一个更优解时就会移动,而最陡上升爬山会在移动前评估所有邻近解并选择最优者。前者更快但可能错过更优解,后者更周全但更慢。可以把简单爬山看作走上发现的第一条上坡路,而最陡上升则是先查看所有路径再选择最陡的一条。

爬山如何应对卡在局部极大值?

当附近没有更优解时,爬山可能会卡在局部极大值(小山包),即使别处存在更好的解。针对这一点,可使用随机重启爬山(从多个随机点开始)与模拟退火(偶尔接受更差解)等技术。这些策略有助于算法探索更广的解空间并找到更好的解。

何时应使用爬山而非其他优化算法?

当问题满足以下条件时,爬山更合适:1)解空间相对平滑、改进渐进;2)可接受快速的近似解;3)问题空间过大不宜穷举;4)计算资源有限。它对超参数调优、投资组合优化与路径规划等问题尤为有效。但若问题复杂且局部最优众多,建议考虑遗传算法或模拟退火等更复杂方法。

如何将爬山算法应用到我的具体问题?

要将爬山应用到您的问题,需定义三个关键组件:1)解的表示(状态);2)评估解优劣的目标函数;3)生成邻近解的方法。以投资组合优化为例,状态是投资权重,目标函数衡量回报与风险,邻居是略有不同的权重分配。本文提供了可按需改编的 Python 实现。

主题
人工智能
Python

热门 AI 课程

学习路径

AI 基础知识

10 小时
探索 AI 基础,学习如何在工作中有效利用 AI,并深入了解 ChatGPT 等模型,以驾驭快速变化的 AI 领域。
查看详情开始课程

创建您的免费账户

实践并学习路径你的进步。

使用 Google 继续
继续操作即表示您接受我们的《使用条款》和《隐私政策》,并同意您的数据存储在美国。
查看更多