游戏AI的创造思路-技术基础-蒙特卡洛树搜索(1)

本篇介绍蒙特卡洛树搜索算法,AlphaGo用于围棋计算的应用就是基于蒙特卡洛树搜索研发的~~~

目录

1. 定义

2. 发展历史

3. 公式和函数

3.1.算法的公式和函数

3.2. Python实现公式和函数

4. 运行原理

4.1. 运行原理

4.2. 各步骤用Python代码

5. 优缺点和缺陷的解决方案

5.1. 优缺点

5.1.1. 优点

5.1.2. 缺点和缺陷

5.2. 缺陷的解决方案

5.2.1. 收敛速度慢

5.2.2. 计算资源消耗大

5.2.3. 探索与利用的平衡

5.2.4. 并行化困难

6.  在游戏AI中的使用实例

6.1. 简单介绍

6.2. 围棋AI中的蒙特卡洛树搜索

Python代码实现


1. 定义

蒙特卡洛树搜索(Monte Carlo Tree Search, MCTS)是一种结合了蒙特卡洛方法和树搜索的算法,特别适用于那些通过模拟能够预测结果的问题,如棋类游戏。

MCTS通过模拟大量随机游戏来评估每个可行的行动,并基于这些模拟结果选择最优行动。

2. 发展历史

蒙特卡洛方法起源于上世纪四十年代中期,为解决当时原子能事业中的复杂问题而发展。

而蒙特卡洛树搜索的概念则是在2006年由Rémi Coulomb提出,作为围棋中的移动规划方法。

近年来,随着AlphaGo等AI系统的成功,MCTS在游戏AI领域的应用得到了广泛关注。

3. 公式和函数

3.1.算法的公式和函数

MCTS算法的核心公式通常包括UCB1(Upper Confidence Bound 1)公式,用于在树搜索过程中平衡开发与探索:

[ UCB1(S_i) = \overline{V_i} + c \sqrt{\frac{\log N}{n_i}} ]

其中,

(\overline{V_i}) 是节点 (S_i)的平均价值,

(c)是常数(通常取2),

(N) 是总探索次数,

(n_i) 是节点 (S_i) 的探索次数。

3.2. Python实现公式和函数

以下是一个简化的Python代码示例,用于实现MCTS中的选择函数,该函数使用UCB1公式来选择子节点:

import math  
  
class Node:  
    def __init__(self, state, parent=None):  
        self.state = state  
        self.parent = parent  
        self.children = []  
        self.visits = 0  
        self.wins = 0  
  
    def ucb1(self, N, c=2):  
        if self.visits == 0:  
            return float('inf')  
        return self.wins / self.visits + c * math.sqrt(math.log(N) / self.visits)  
  
def select(node, N):  
    while node.children:  
        best_child = max(node.children, key=lambda c: c.ucb1(N))  
        node = best_child  
    return node

4. 运行原理

4.1. 运行原理

MCTS的运行原理主要包括四个步骤:选择(Selection)、扩展(Expansion)、模拟(Simulation)、反向传播(BackPropagation)。

  1. 选择(Selection):从根节点开始,使用UCB1公式递归选择最优子节点直到叶子节点。

  2. 扩展(Expansion):如果当前叶子节点不是终止节点,则创建一个或多个子节点,并选择其中一个进行扩展。

  3. 模拟(Simulation):从扩展节点开始,随机模拟游戏直到结束,记录模拟结果。

  4. 反向传播(BackPropagation):将模拟结果反向传播,更新所有祖先节点的统计信息。

4.2. 各步骤用Python代码

以下是简化版的MCTS实现框架:

import math

# 节点类定义
class Node:  
    def __init__(self, state, parent=None):  
        self.state = state  
        self.parent = parent  
        self.children = []  
        self.visits = 0  
        self.wins = 0  
  
    def is_terminal(self):  
        # 实现判断节点是否为终止节点的逻辑  
        # 这里只是示例,具体实现需要根据游戏规则来  
        return False  
  
    def uct_value(self, parent_visits):  
        if self.visits == 0:  
            return float('inf')  # 未访问过的节点UCB值设为无穷大  
        win_rate = self.wins / self.visits  
        exploration_factor = 2  # 探索因子,可以调整  
        return win_rate + exploration_factor * sqrt(log(parent_visits) / self.visits) 

# 算法函数定义
def mcts(root, num_iterations):  
    for _ in range(num_iterations):  
        node = root  
        # 选择Selection  
        while node.children:  
            node = select(node, node.visits)  
  
        # 扩展Expansion  
        if not node.is_terminal():  
            node = expand(node)  
  
        # 模拟Simulation  
        result = simulate(node.state)  
  
        # 反向传播Backpropagation  
        backpropagate(node, result)  

# 选择逻辑函数
def select(node, parent_visits):  
    # 实现选择逻辑,这里使用UCT(Upper Confidence Bound for Trees)  
    return max(node.children, key=lambda child: child.uct_value(parent_visits))  

# 节点扩展逻辑
def expand(node):  
    # 实现节点扩展逻辑 
    # 这里只是示例,具体实现需要根据游戏规则来生成新的状态  
    new_state = node.state.generate_next_state()  # 假设状态对象有一个生成下一个状态的方法  
    new_node = Node(new_state, node)  
    node.children.append(new_node)  
    return new_node  

# 模拟逻辑函数  
def simulate(state):  
    # 实现模拟逻辑
    # 从当前节点的状态开始模拟  
    current_state = node.state  
      
    # 模拟游戏进行,直到达到终端状态  
    while not current_state.is_terminal():  
        # 根据当前状态随机选择一个动作  
        action = current_state.sample_random_action()  
          
        # 执行动作,得到新的状态  
        current_state = current_state.apply_action(action)  
      
    # 返回模拟结果,通常是胜负标识  
    return current_state.get_result()    

# 反向传播函数  
def backpropagate(node, result):  
    while node:  
        node.visits += 1  
        if result > 0:  # 假设result为正表示胜利  
            node.wins += 1  
        node = node.parent
 
#-------------------------------------------------------  
# 示例用法  
root = Node(initial_state)  # 假设有一个初始状态  
num_iterations = 1000  
mcts(root, num_iterations)

5. 优缺点和缺陷的解决方案

5.1. 优缺点

5.1.1. 优点

  1. 高效性:MCTS通过模拟大量随机游戏来评估每个可行的行动,相比其他算法,它能够在较短的时间内找到相对较好的解决方案。这种高效性使得MCTS在实时决策系统中特别有用,如游戏AI和自动驾驶。

  2. 通用性:MCTS不需要对问题的具体特性有先验知识,因此可以灵活应用于多种领域,包括博弈游戏、组合优化、决策制定等。它的通用性使得MCTS成为许多复杂问题求解的有力工具。

  3. 自我提升:MCTS通过不断模拟和自我对弈来提升样本的利用率,从而改善状态空间建模、策略提升和探索/利用效率等方面的性能。这种自我提升机制使得MCTS能够在面对新问题时逐渐适应并找到更好的解决方案。

  4. 动态适应:MCTS能够动态地调整搜索策略,专注于那些获得高评估回报的仿真结果,并基于这些结果不断向外扩展搜索树。这种动态适应性使得MCTS在复杂多变的环境中表现出色。

5.1.2. 缺点和缺陷

  1. 收敛速度慢:对于复杂的问题,MCTS往往需要大量的模拟才能达到可靠的估值,这可能导致收敛速度较慢。在某些情况下,为了在有限时间内做出决策,可能需要牺牲一定的解的质量。

  2. 计算资源消耗大:为了达到较高的决策质量,MCTS需要进行大量的模拟,这在处理复杂的游戏或问题时,会消耗大量的CPU时间和内存资源。这限制了MCTS在某些资源受限环境中的应用。

  3. 探索与利用的平衡:传统的MCTS算法需要在搜索树的每个节点上进行平衡探索(exploration)与利用(exploitation)的决策。当搜索空间庞大时,如何有效地维持这种平衡是一个挑战。如果探索过多,可能会导致收敛速度变慢;如果利用过多,则可能陷入局部最优解。

  4. 并行化困难:尽管MCTS算法在理论上可以并行化,但在实际操作中,如何有效地管理并行计算以减少性能损失仍是一个难题。特别是如何确保每个并行计算单元都能使用最新的统计数据来进行有效的探索和利用权衡。

5.2. 缺陷的解决方案

针对蒙特卡洛树搜索(Monte Carlo Tree Search, MCTS)的缺点和缺陷,以下是一些具体的解决方案:

5.2.1. 收敛速度慢

解决方案及实施步骤

  • 结合领域知识
    • 步骤一:收集和分析特定领域的先验知识,如游戏规则、专家经验等。
    • 步骤二:在MCTS的模拟过程中,将这些先验知识融入模拟策略中,例如通过调整模拟动作的选择概率来引导模拟过程。
    • 步骤三:评估结合领域知识后的模拟效果,根据反馈调整先验知识的应用方式。
  • 使用渐进展开
    • 步骤一:在搜索过程中,为每个节点维护统计信息,如访问次数、胜利次数等。
    • 步骤二:根据节点的统计信息,优先扩展那些具有更高潜力的节点,即访问次数较少但胜利次数较多的节点。
    • 步骤三:随着搜索的进行,逐步展开其他节点,确保搜索过程的全面性和有效性。
  • 动态调整模拟次数
    • 步骤一:为每个节点设置初始模拟次数,并根据搜索进度和结果进行调整。
    • 步骤二:在搜索初期,增加模拟次数以充分探索搜索空间;在搜索后期,减少模拟次数以加快收敛速度。
    • 步骤三:监控搜索过程的收敛情况,根据实时反馈动态调整模拟次数。

5.2.2. 计算资源消耗大

解决方案及实施步骤

  • 并行化计算
    • 步骤一:将MCTS算法分解为可并行化的子任务,如多个模拟实例可以同时运行。
    • 步骤二:利用现代计算机的多核处理器或分布式计算资源来并行执行这些子任务。
    • 步骤三:设计有效的数据同步和通信机制,确保并行计算单元之间的协作和数据一致性。
  • 剪枝技术
    • 步骤一:分析MCTS搜索过程中的无效搜索分支,识别出可以剪枝的节点。
    • 步骤二:开发适用于MCTS的剪枝策略,如基于节点统计信息的剪枝规则。
    • 步骤三:在搜索过程中应用剪枝策略,减少不必要的搜索分支,降低计算资源消耗。
  • 自适应终止条件
    • 步骤一:为每个节点设置自适应的模拟终止条件,如基于模拟次数、时间限制或节点统计信息的终止规则。
    • 步骤二:在模拟过程中监控这些终止条件,一旦满足条件则提前终止模拟过程。
    • 步骤三:评估自适应终止条件对搜索效果和计算资源消耗的影响,根据反馈调整终止条件。

5.2.3. 探索与利用的平衡

解决方案及实施步骤

  • 使用UCB公式变体
    • 步骤一:分析传统UCB公式的优缺点,并探索其他形式的UCB公式变体。
    • 步骤二:在MCTS算法中引入新的UCB公式变体,调整其中的参数以平衡探索和利用。
    • 步骤三:评估新公式变体对搜索效果的影响,根据反馈调整参数和公式形式。
  • 渐进式调整探索率
    • 步骤一:为MCTS算法设置初始探索率,该探索率决定了随机选择动作的概率。
    • 步骤二:在搜索过程中,根据搜索进度和结果渐进式地调整探索率。例如,随着搜索的深入,逐渐降低探索率以增加利用已有信息的比重。
    • 步骤三:监控搜索过程中的探索与利用情况,确保在保持多样性的同时提高搜索效率。
  • 结合其他搜索策略
    • 将MCTS与其他搜索策略(如贪婪搜索、局部搜索等)相结合,利用各自的优势来弥补不足。
    • 例如,可以在MCTS的模拟阶段使用局部搜索策略来快速找到当前状态下的局部最优解。

5.2.4. 并行化困难

解决方案及实施步骤

  • 设计有效的并行化策略
    • 步骤一:分析MCTS算法的并行化需求,识别出可并行化的部分。
    • 步骤二:设计合理的任务划分和数据同步策略,确保并行计算单元之间的有效协作。
    • 步骤三:实现并行化MCTS算法,并在实际应用中测试其性能和效果。
  • 利用现代并行计算框架
    • 步骤一:选择合适的并行计算框架(如MPI、OpenMP、CUDA等),根据具体需求和环境进行配置。
    • 步骤二:将MCTS算法转换为适合并行计算的形式,并利用并行计算框架提供的接口和工具进行实现。
    • 步骤三:优化并行算法的性能,包括减少通信开销、平衡负载、优化数据访问模式等。
  • 处理数据一致性问题
    • 在并行化MCTS过程中,需要特别注意数据一致性问题。
    • 通过引入适当的同步机制和锁策略来确保并行计算单元之间访问共享数据时的正确性。
    • 同时,还需要优化数据访问模式以减少缓存不一致性和内存带宽瓶颈等问题的影响。

6.  在游戏AI中的使用实例

6.1. 简单介绍

蒙特卡洛树搜索(Monte Carlo Tree Search,MCTS)在游戏AI中是一个强大的算法,尤其适用于那些具有庞大状态空间和/或难以评估状态价值的游戏。

一个典型的使用实例是将其应用于围棋、国际象棋或类似的策略棋类游戏。

下面,我将通过一个简化的围棋游戏AI的实例来展示蒙特卡洛树搜索的应用,并提供相应的Python代码实现。

6.2. 围棋AI中的蒙特卡洛树搜索

在围棋中,MCTS用于搜索可能的走子序列,并评估这些序列的胜负概率。算法的核心在于通过模拟(即随机走子至游戏结束)来评估节点价值,并利用这些模拟结果来指导搜索。

Python代码实现

以下是一个围棋MCTS实现的框架:

import random  
from math import sqrt, log  
  
class GameState:  
    def __init__(self, board=None, turn=None, last_move=None):  
        self.board = board or self.create_empty_board()  
        self.turn = turn or 'X'  # 'X' 或 'O'  
        self.last_move = last_move  
  
    def create_empty_board(self):  
        # 创建一个空的围棋棋盘,例如 9x9  
        return [['.' for _ in range(9)] for _ in range(9)]  
  
    def is_terminal(self):  
        # 检查游戏是否结束(简化版本,实现基本的胜负判断)  
        # 这里只检查是否所有位置都已落子,实际游戏需要更复杂的判断  
        return all(cell != '.' for row in self.board for cell in row)  
  
    def generate_moves(self):  
        # 生成所有可能的走子  
        moves = []  
        for i in range(len(self.board)):  
            for j in range(len(self.board[0])):  
                if self.board[i][j] == '.':  
                    moves.append((i, j))  
        return moves  
  
    def apply_move(self, move):  
        # 应用走子并返回新的游戏状态  
        new_board = [row[:] for row in self.board]  
        new_board[move[0]][move[1]] = self.turn  
        return GameState(new_board, 'O' if self.turn == 'X' else 'X', move)  
  
    def get_result(self):  
        # 返回游戏结果,简化版本只返回胜负  
        if self.is_terminal():  
            # 这里应该实现真正的胜负判断,简化版本只返回 None  
            return None  # 实际上应该根据棋盘状态判断胜负  
        return None  
  
    def __str__(self):  
        # 打印棋盘状态  
        return '\n'.join(''.join(row) for row in self.board)  
  
class Node:  
    def __init__(self, game_state, parent=None):  
        self.game_state = game_state  
        self.parent = parent  
        self.children = []  
        self.visits = 0  
        self.wins = 0  
  
    def is_terminal(self):  
        return self.game_state.is_terminal()  
  
    def uct_value(self, parent_visits):  
        if self.visits == 0:  
            return float('inf')  
        win_rate = self.wins / self.visits  
        exploration_factor = sqrt(2)  # 探索与利用的权衡因子  
        return win_rate + exploration_factor * sqrt(log(parent_visits) / self.visits)  
  
    def expand(self):  
        moves = self.game_state.generate_moves()  
        for move in moves:  
            next_state = self.game_state.apply_move(move)  
            child_node = Node(next_state, self)  
            self.children.append(child_node)  
  
    def select_child(self):  
        return max(self.children, key=lambda child: child.uct_value(self.visits))  
  
def simulate(node):  
    current_node = node  
    while not current_node.is_terminal():  
        if not current_node.children:  
            current_node.expand()  
        current_node = current_node.select_child()  
    # 在这个简化版本中,我们总是返回 None,因为胜负判断未实现  
    return None  
  
def backpropagate(node, result):  
    while node:  
        node.visits += 1  
        if result is not None:  # 在这个简化版本中,我们从不更新胜利次数,因为胜负判断未实现  
            node.wins += result  # 实际上这里应该是 1 或 0,表示胜利或失败  
        node = node.parent  
  
def mcts(root, num_iterations):  
    for _ in range(num_iterations):  
        node = root  
        while not node.is_terminal():  
            if not node.children:  
                node.expand()  
            node = node.select_child()  
        result = simulate(node)  # 在这个简化版本中,simulate 总是返回 None  
        backpropagate(node, result)  
  
# 示例用法  
initial_state = GameState()  
root = Node(initial_state)  
num_iterations = 1000  
mcts(root, num_iterations)  
best_child = max(root.children, key=lambda child: child.visits)  
print("推荐走子:", best_child.game_state.last_move)  
print("棋盘状态:\n", best_child.game_state)

在这个实现中,GameState类提供了创建空棋盘、检查游戏是否结束、生成所有可能的走子、应用走子并返回新的游戏状态以及获取游戏结果的功能。

Node类和MCTS算法的实现与之前提供的示例相同。

在示例用法中,我们创建了一个初始的游戏状态,并进行了1000次MCTS迭代来选择最佳的走子。最后,我们打印了推荐的走子和当前的棋盘状态。

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

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

相关文章

C语言-预处理详解

文章目录 🎯引言👓预处理详解1.预定义符号1.1 __FILE__1.2 __LINE__1.3 __DATE__1.4 __TIME__1.5 __STDC__ 2.#define定义常量2.1 定义数值常量2.2 定义字符串常量 3.#define中使用参数3.1**使用示例**3.2注意事项 4.宏替换的规则5.宏函数和函数的对比5.…

使用Redis实现消息队列:List、Pub/Sub和Stream的实践

摘要 Redis是一个高性能的键值存储系统,它的多种数据结构使其成为实现消息队列的理想选择。本文将探讨如何使用Redis的List、Pub/Sub和Stream数据结构来实现一个高效的消息队列系统。 1. 消息队列的基本概念 消息队列是一种应用程序之间进行通信的机制&#xff0…

解锁算力新极限,Xilinx UltraScale+赋能的高性能低延时FPGA加速卡

01、产品概述 AiHPC-V9P 是一款基于 AMD Virtex UltraScale FPGA VU9P 的 PCIe Gen3.0 x16 接口智能网卡,具有最大2*200GbE /或者16*10GbE(典型应用)接入容量的高性能低延时智能网卡。 对外接口支持两组QSFP-DD 最高25Gb/s x8Lane 光口接入&#xf…

Java基础-组件及事件处理(中)

(创作不易,感谢有你,你的支持,就是我前行的最大动力,如果看完对你有帮助,请留下您的足迹) 目录 BorderLayout布局管理器 说明: 示例: FlowLayout布局管理器 说明: …

我跟ai学web知识点:“短链接”

我跟ai学web知识点,短链接不是“免费午餐”。 (笔记模板由python脚本于2024年07月08日 12:44:47创建,本篇笔记适合喜欢Web知识的coder翻阅) 【学习的细节是欢悦的历程】 Python 官网:https://www.python.org/ Free:大咖免费“圣经…

Windows下编译OpenSSL静态库

目录 1. 版本与下载地址 2. 下载与安装VS2015 3. 下载与安装Perl 4. 测试ActivePerl是否安装正确 5. 下载OpenSSL 6. 编译32位OpenSSL静态库 6.1 解压openssl-1.0.2l.tar.gz 6.2 打开VS2015 x86本机工具命令提示符 6.3 输入命令进入到openssl的目录中 6.4 执行配置命…

一文洞悉巴基斯坦电子游戏出海引流获客广告风口不容忽视

一文洞悉巴基斯坦电子游戏出海引流获客广告风口不容忽视 随着全球数字经济的蓬勃发展,电子游戏行业也迎来了前所未有的机遇。巴基斯坦,这个拥有庞大人口基数和日益增长的消费能力的国家,其电子游戏市场潜力巨大。本文旨在探讨巴基斯坦电子游戏…

tomcat安装

tomcat tomcat和php一样,都是用来处理动态页面的。 tomcat也可以作为web应用服务器,开源的。 php .php tomcat .jsp nginx .html tomcat是用java代码写的程序,运行的是java的web应用程序 tomcat的特点和功能: 1、servlet容…

首席数据官CDO证书报考指南:方式、流程、适考人群与考试难度

在信息泛滥的今天,数据已转变为企业不可或缺的宝贵资源。 面对海量的信息,如何提炼出价值,为企业带来实质性的收益?首席数据官(CDO)认证的出现正是为了满足这一需求,它不仅是个人专业能力的体现…

AI Earth——1990-2022年全国月度气象数据检索应用app

应用结果 代码 #导入安装包 import os import json import datetime import streamlit as st import streamlit.components.v1 as components import traceback from PIL import Imageimport aie#读取当前目录的内容 current_work_dir = os.path.dirname(__file__) #添加地图…

简单的Java面向对象小游戏并使用三层架构(表示层、业务逻辑层、数据访问层)

本人详解 作者:王文峰,参加过 CSDN 2020年度博客之星,《Java王大师王天师》 公众号:JAVA开发王大师,专注于天道酬勤的 Java 开发问题中国国学、传统文化和代码爱好者的程序人生,期待你的关注和支持!本人外号:神秘小峯 山峯 转载说明:务必注明来源(注明:作者:王文峰…

AE-时间轴的基础操作

目录 预览(快捷键空格) 调整时间线显示比例(Alt鼠标滚轮) 控制预览长度(B/N) 逐帧移动(笔记本:按住fn上下方向键) 视频剪切(ctrlshiftD) 剪…

数据结构:顺序表+链表

数据结构:顺序表链表 一。顺序表: 首先在了解顺序表和链表之前,先了解一下线性表,**线性表(linear list)**是n个具有相同特征元素的有限序列 ,在逻辑上是线性结构,也就是一条连续的…

深入剖析预处理

目录 1.预定义符号 2.#define 定义常量 3.#define定义宏 4.带有副作用的宏参数 5.宏替换的规则 6.宏函数的对比 7.#和## 7.1 #运算符 7.2 ## 运算符 8.命名约定 9.#undef 10.命令行定义 11.条件编译 12.头文件的包含 12.1 头文件被包含的方式: 12.1…

React setState

老生常谈之setState 是同步的还是异步的? 设想setState是同步的,那也就是每次调用setState都要进行新旧虚拟DOM的对比,然后将差异化的dom更新到页面上,性能损耗很大 所以react把setState设置为了异步,当状态更新时不…

基于springboot+vue实现的厨艺交流平台(文末源码+Lw)093

93基于SpringBootVue的实现的厨艺交流平台(源码数据库万字Lun文流程图ER图结构图演示视频软件包) 系统功能: 这次开发的厨艺交流平台功能有个人中心,食材分类管理,用户管理,菜品分类管理,菜谱信…

【Axure】产品原型如何在谷歌浏览器中打开

作为一名前端开发来说,在拿到产品的原型图后,如何打开?直接用谷歌浏览器打开,是打不开的,需要安装对应的插件。但是谷歌插件市场在不翻墙的情况下,是没有办法直接打开的,分享一种超级简单的方法…

Softmax回归中的损失函数

目录 一、损失函数介绍: 因为Softmax回归时逻辑回归的推广,所以Softmax回归的损失函数是逻辑回归损失函数的推广。 原文链接:逻辑回归中的损失函数 一、损失函数介绍: 与回归问题成本函数不同的是,Softmax回归模型&a…

python-小杨的储蓄(赛氪OJ)

题目描述 小杨共有 N 个储蓄罐,编号从 0 到 N−1。从第 1 天开始,小杨每天都会往存钱罐里存钱。具体来说,第 i 天他会挑选一个存钱罐 ai​,并存入 i 元钱。过了 D 天后,他已经忘记每个储蓄罐里都存了多少钱了&#xff…

如何网页在线编辑微软Office Word,并导出为PDF格式。

随着互联网技术的不断发展,越来越多的企业开始采用在线办公模式,微软Office Word 是最好用的文档编辑工具,然而doc、docx、xls、xlsx、ppt、pptx等格式的Office文档是无法直接在浏览器中直接打开的,如果可以实现Web在线预览编辑Of…