多臂老虎机

news2024/11/19 9:30:58

多臂老虎机

有n根拉杆的的老虎机,每根拉杆获得奖励(值为1)的概率各不相同。

期望奖励更新
Q k = 1 k ∑ i = 1 k r i = 1 k ( r k + ∑ i = 1 k − 1 r i ) = 1 k ( r k + k Q k − 1 − Q k − 1 ) = Q k − 1 + 1 k [ r k − Q k − 1 ] Q_k=\frac 1k \sum^{k}_{i=1}r_i\\ =\frac 1k (r_k+\sum^{k-1}_{i=1}r_i)\\ =\frac 1k( r_k+kQ_{k-1}-Q_{k-1})\\ =Q_{k-1}+\frac 1k[r_k-Q_{k-1}] Qk=k1i=1kri=k1(rk+i=1k1ri)=k1(rk+kQk1Qk1)=Qk1+k1[rkQk1]
累计懊悔:

​ 至少存在一根拉杆,它的期望奖励不小于拉动其他任意一根拉杆,设最优期望为 Q ∗ = m a x a ∈ A Q ( a ) Q^*=max_{a\in A}Q(a) Q=maxaAQ(a),在此基础上,引入懊悔概念,被定义为当前的收益与最优期望的插值,即 R ( a ) = Q ∗ − Q ( a ) R(a)=Q^*-Q(a) R(a)=QQ(a),累计懊悔 σ R = ∑ t = 1 T R ( a t ) \sigma_R=\sum^T_{t=1}R(a_t) σR=t=1TR(at),则多臂老虎机可等价为最小化累积期望。

基本框架

多臂老虎机的基本框架如下

import numpy as np
import matplotlib.pyplot as plt


class BernoulliBandit:
    """k表示拉杆个数"""

    def __init__(self, K):
        self.probs = np.random.uniform(size=K)  # 随机生成K个0到1的数作为概率,获奖积1分,没获奖0分
        self.best_idx = np.argmax(self.probs)  # 获奖概率最大的拉杆
        self.best_prob = self.probs[self.best_idx]  # 最大获奖概率
        self.K = K

    def step(self, K):
        # 进行一步,根据选择的第k号拉杆获奖的概率进行返回
        if np.random.rand() < self.probs[K]:
            return 1
        else:
            return 0


np.random.seed(1)  # 设置一样的随机种子,使得实验可重复
K = 10
bandit_10_arm = BernoulliBandit(K)
print("随机生成了一个%d臂伯努利老虎机" % K)
print("获奖概率最大的拉杆为%d号,其获奖概率为%.4f" % (bandit_10_arm.best_idx, bandit_10_arm.best_prob))


class Solver:
    # 运行多臂老虎机的基本框架
    def __init__(self, bandit):
        self.bandit = bandit
        self.counts = np.zeros(self.bandit.K)  # 初始化每根拉杆的尝试次数
        self.regret = 0  # 当前的累计懊悔
        self.actions = []  # 维护一个列表,记录每一步的动作
        self.regrets = []  # 记录每一步的累计懊悔

    def update_regret(self, k):
        # 计算累计懊悔并保存,k为当前选择的拉杆编号
        self.regret += self.bandit.best_prob - self.bandit.probs[k]  # 计算懊悔值
        self.regrets.append(self.regret)

    def run_one_step(self):
        # 返回当前动作选择哪一根拉杆,由具体策略实现
        raise NotImplementedError  # ?问下gpt

    def run(self, num_steps):
        # 运行一定次数
        for _ in range(num_steps):
            k = self.run_one_step()
            self.counts[k] += 1
            self.actions.append(k)
            self.actions.append(k)
            self.update_regret(k)


def plot_results(solvers, solver_names):
    # 输入的solvers是一个列表,每个元素是一个特定策略,solver_names也是一个列表,存储每个策略的名称
    for idx, solver in enumerate(solvers):
"""enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般在循环中使用。此处将索引存在idx,具体的策略存在solver中"""
        time_list = range(len(solver.regrets))  # 生成一个包含从 0 到 solver.regrets 的大小减去 1 的整数序列。
        plt.plot(time_list, solver.regrets, label=solver_names[idx])
    plt.xlabel("Time steps")
    plt.ylabel("Cumulative regrets")
    plt.title("%d-armed bandit" % solvers[0].bandit.K)
    plt.legend()  # 用于添加图例到 Matplotlib 图表中的函数,即使用label标签标记的。
    plt.show()

​ 对于多臂老虎机的决策,有如下方法

ϵ \epsilon ϵ-贪婪算法

​ 这个算法设置一个随机概率 ϵ \epsilon ϵ,每次做决策时,有 1 − ϵ 1-\epsilon 1ϵ的概率按已有经验做出最优决策,在这个问题中,即在拉动过的杆中选出最优的,以概率 ϵ \epsilon ϵ随机选择一根拉杆。

​ 本例中先选择 ϵ = 0.01 , T = 5000 \epsilon =0.01,T=5000 ϵ=0.01,T=5000

class EpsilonGreedy(Solver):
    """epsilon贪婪算法,需要继承Solver类"""

    def __init__(self, bandit, epsilon=0.01, init_prob=1.0):
        super(EpsilonGreedy, self).__init__(bandit)  # 调用父类的构造函数,用bandit作为参数
        self.epsilon = epsilon
        self.estimates = np.array([init_prob] * self.bandit.K)  # 初始化所有拉杆的期望奖励估值,设置为一样的?

    def run_one_step(self):
        # 以贪婪算法运行一步
        if np.random.rand() < self.epsilon:  # 随机选择
            k = np.random.randint(0, self.bandit.K)
        else:
            k = np.argmax(self.estimates)  # 选择期望奖励最大的
        r = self.bandit.step(k)  # 获得奖励
        self.estimates[k] += 1. / (self.counts[k] + 1) * (r - self.estimates[k])
        return k  # 返回选择的杆的编号
      
np.random.seed(1)
epsilon_greedy_solver = EpsilonGreedy(bandit_10_arm, epsilon=0.01)
epsilon_greedy_solver.run(5000)
print('epsilon-贪婪算法的累计懊悔为:', epsilon_greedy_solver.regret)
plot_results([epsilon_greedy_solver], ["EpsilonGreedy"])

在这里插入图片描述

​ 实际上,尝试次数越多,越不需要冒险去探险,因为这无疑会降低收益,那么我们希望 ϵ \epsilon ϵ随时间逐渐变小。即 ϵ t = 1 t \epsilon_t = \frac 1t ϵt=t1,一般使用随时间衰减的贪婪算法:

class DecayingEpsilonGreedy(Solver):
    """epsilon值随时间衰减的贪婪算法,需要继承Solver类"""

    def __init__(self, bandit, init_prob=1.0):
        super(DecayingEpsilonGreedy, self).__init__(bandit)  # 调用父类的构造函数,用bandit作为参数
        self.estimates = np.array([init_prob] * self.bandit.K)  # 初始化所有拉杆的期望奖励估值,设置为一样的?
        self.total_count = 0

    def run_one_step(self):
        # 以贪婪算法运行一步
        self.total_count += 1
        if np.random.rand() < 1 / self.total_count:  # 随机选择
            k = np.random.randint(0, self.bandit.K)
        else:
            k = np.argmax(self.estimates)  # 选择期望奖励最大的
        r = self.bandit.step(k)  # 获得奖励
        self.estimates[k] += 1. / (self.counts[k] + 1) * (r - self.estimates[k])
        return k  # 返回选择的杆的编号

np.random.seed(1)
decaysing_epsilon_greedy_solver = DecayingEpsilonGreedy(bandit_10_arm)
decaysing_epsilon_greedy_solver.run(5000)
print('epsilon值衰减的-贪婪算法的累计懊悔为:', decaysing_epsilon_greedy_solver.regret)
plot_results([decaysing_epsilon_greedy_solver], ["DecayingEpsilonGreedy"])

在这里插入图片描述

上置信界算法(UCB)

​ 在探索时,我们总会有这样的一种想法,如果第1根拉杆只被拉动过1次,但奖励为0,第2根被拉动过很多次,我们对它的奖励分布已经有了比较确定的把握,或许我们更愿意尝试拉动第一根拉杆,可能他的收益更高。为了定量的描述这种“潜藏“的更优解,引入了不确定性度量 U ( a ) U(a) U(a),它会随着一个动作被尝试次数的增加而减小。

​ 那么我们可以使用一种基于不确定性的策略来综合考虑现有的期望奖励估值和不确定性,其核心问题是如何估计不确定性。

霍夫丁不等式

​ 令 X 1 , ⋯   , X n X_1,\cdots,X_n X1,,Xn n n n个独立同分布的随机变量,取值范围为 [ 0 , 1 ] [0,1] [0,1],若经验期望为 x ‾ = 1 x ∑ j = 1 n X j \overline{x}=\frac 1x\sum^n_{j=1}X_j x=x1j=1nXj,则有
P ( E [ X ] ≥ x ‾ t + u ) ≤ e − 2 n u 2 P(E[X]\ge \overline{x}_t +u)\le e^{-2nu^2} P(E[X]xt+u)e2nu2

期望的奖励上界

​ 在这个例子中,经验期望就是 Q ^ ( a t ) \hat{Q}(a_t) Q^(at),将 u = U ^ ( a t ) u=\hat{U}(a_t) u=U^(at)代表不确定性度量,给定一个概率 p = e − 2 N ( a t ) U ( a t ) 2 p=e^{-2N(a_t)U(a_t)^2} p=e2N(at)U(at)2,其中 N ( a t ) N(a_t) N(at)表示拉动某一编号杆的次数。根据霍夫丁不等式, Q ( a t ) < Q ^ ( a t ) + U ^ ( a t ) Q(a_t)<\hat{Q}(a_t)+\hat{U}(a_t) Q(at)<Q^(at)+U^(at)至少以概率 1 − p 1-p 1p成立,当 p p p很小时,以很大概率成立,那么我们可以认为 Q ^ ( a t ) + U ^ ( a t ) \hat{Q}(a_t)+\hat{U}(a_t) Q^(at)+U^(at)便是期望的奖励上界。

​ 此时,UCB算法选取上界最大的动作,即 a t = a r g   m a x a ∈ A [ Q ^ ( a ) + U ^ ( a ) ] a_t = arg\ max_{a\in A}[\hat{Q}(a)+\hat{U}(a)] at=arg maxaA[Q^(a)+U^(a)]

​ 其中 U ^ ( a t ) = − l o g   p 2 N ( a t ) \hat{U}(a_t)=\sqrt{\cfrac{-log\ p}{2N(a_t)}} U^(at)=2N(at)log p ,也就是说,设定一个概率 p p p后,就可计算了。

​ 总的来说,UCB算法在每次决策前,都会估计每根杆的期望上界,使得拉动每根杆的期望奖励只有一个较小的概率 p p p超过上界,并选取最优可能获得最大期望奖励的拉杆。

UCB算法的代码实现

​ 容易发现的是,随着时间的增长,我们将对期望有着越来越确定的把握,那么 p p p的设置也应该是随时间增长减少的,则设 p = 1 t p=\frac 1t p=t1

​ 对于 U ^ ( a t ) \hat{U}(a_t) U^(at),我们在分母加上常数1,避免出现分母为0的情况,则 U ^ ( a t ) = l o g t 2 ( N ( a t ) + 1 ) \hat{U}(a_t)=\sqrt{\frac{log t}{2(N(a_t)+1)}} U^(at)=2(N(at)+1)logt

​ 为了控制不确定性的比重,引入系数 c c c,此时, a t = a r g   m a x a ∈ A [ Q ^ ( a ) + c ⋅ U ^ ( a ) ] a_t = arg\ max_{a\in A}[\hat{Q}(a)+c\cdot \hat{U}(a)] at=arg maxaA[Q^(a)+cU^(a)]

class UCB(Solver):
    # UCB算法,继承Solver类
    def __init__(self, bandit, coef, init_prob=1.0):
        super(UCB, self).__init__(bandit)
        self.total_count = 0  # 时间计数t
        self.estimates = np.array([init_prob] * self.bandit.K)  # 一样的初始化期望
        self.coef = coef

    def run_one_step(self):
        self.total_count += 1
        ucb = self.estimates + self.coef * np.sqrt(np.log(self.total_count) / (2 * (self.counts + 1)))  # 计算上置信界
        # np.log()默认以e为底,以其他为底可使用换底公式
        k = np.argmax(ucb)  # 选出上置信界最大的拉杆
        r = self.bandit.step(k)
        self.estimates[k] += 1. / (self.counts[k] + 1) * (r - self.estimates[k])
        return k


np.random.seed(1)
coef = 0.7  # 不确定性权重的参数
UCB_solver = UCB(bandit_10_arm, coef)
UCB_solver.run(5000)
print("上置信界算法的累积懊悔为:", UCB_solver.regret)
plot_results([UCB_solver], ["UCB"])

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

STL----resize

resize的作用 设置容器元素个数和初始值。 resize和reserve resize即改变容器元素个数&#xff0c;也改变容器容量。 reserve只改变容器容量&#xff0c;不改变容器元素个数。 reserve有什么用 reserve---存储&#xff0c;容量&#xff0c;保留。 1&#xff0c;设置容器容…

【vivado】 IBERT GT收发器误码率测试

一、前言 IBERT(Integrated Bit Error Ratio Tester),集成误码率测试仪。作为用户来说可以使用这个工具对自己设计的板子中的高速串行收发器进行简单测试&#xff0c;从而判断设计的接口是否有问题。因为这个工具是直接集成到FPGA上&#xff0c;这样一来直接使用这个工具来测试…

搜索引擎的设计与实现(三)

目录 5 系统详细实现 5.1实现环境配置 5.2功能实现 5.2.1 建立索引 5.2.2 文件搜索实现 5.2.3 数据库的连接配置 5.2.4 数据库搜索实现 5.2.5 后台数据编辑实现 前面内容请移步 搜索引擎的设计与实现&#xff08;二&#xff09; 免费源代码&毕业设计论文 搜索…

vaspkit 画 Charge-Density Difference

(echo 314;echo $(cat 1))|vaspkit 文件1提前写好使用的CHGCAR路径 SPIN_DW.vasp ../ML2scf/SPIN_DW.vasp ../ML1scf/SPIN_DW.vasp POSite and negative 默认为blue,and 青色 (RGB 30 245 245) 正值&#xff1a;blue 。负值&#xff1a;青色 RGB 30 245 245。 提示&…

(四十二)第 6 章 树和二叉树(树的二叉链表(孩子-兄弟)存储)

1. 背景说明 2. 示例代码 1) errorRecord.h // 记录错误宏定义头文件#ifndef ERROR_RECORD_H #define ERROR_RECORD_H#include <stdio.h> #include <string.h> #include <stdint.h>// 从文件路径中提取文件名 #define FILE_NAME(X) strrchr(X, \\) ? strrch…

Nodejs笔记2

模块化 模块化初体验 模块暴露数据 导入模块 fs 写绝对路径 require写相对路径不会受到影响 ./../不能省略 js 和json文件后缀可以省略 如果存在 命名相同的js和json文件&#xff0c;优先导入js文件 导入文件夹时的情况 require导入模块的基本流程 commonJS模块…

大数据可视化实验(四):Excel数据可视化

目录 一、实验目的... 1 二、实验环境... 1 三、实验内容... 1 1&#xff09;excel函数应用.. 1 2&#xff09;数据透视图绘制... 3 四、总结与心得体会... 5 一、实验目的 1&#xff09;掌握函数和公式的原理 2&#xff09;掌握在单元格或编辑栏中直接输入带函数的公式…

react18【系列实用教程】react-router-dom —— 路由管理 (2024最新版)

类似 vue-router 安装 npm i react-router-domreact-router 中包含 native 的开发&#xff0c;仅网站开发&#xff0c;使用更轻量的 react-router-dom 即可 路由模式 history 模式需要后端支持&#xff0c;使用 createBrowserRouter 函数实现hash 模式无需后端支持&#xff0c;…

TypeScript学习日志-第二十六天(weakMap,weakSet,set,map)

weakMap,weakSet,set,map 一、set set 的基本用法如下&#xff1a; 二、map map 与 set 的 区别 就是 map 的 key 可以是引用类型 object array , map 的添加时使用 set 三、weakmap weakset weakmap和weakset 都是弱项 弱引用 其键必须是引用类型&#xff0c;不能是其它类…

RT Thread + CLion环境搭建

RT Thread CLion环境搭建 0.前言一、准备工具1. Env RT Thread v5.12.CLion安装3.编译及下载工具 二、新建Env工程三、CLion配置四、运行测试 0.前言 事情的起因是最近在使用RT Thread Studio时&#xff0c;发现默认的 rtt 内核版本及交叉编译链版本都过于陈旧&#xff0c;于…

【基本数据结构】链表

文章目录 前言链表简介头节点与尾节点特性 分类单向链表双向链表循环链表 单链表基本操作定义并初始化单链表读取节点插入节点删除节点修改节点 参考资料写在最后 前言 本系列专注更新基本数据结构&#xff0c;现有以下文章&#xff1a; 【算法与数据结构】数组. 【算法与数…

软件工程期末复习(6)需求分析的任务

需求分析 需求分析的任务 “建造一个软件系统的最困难的部分是决定要建造什么……没有别的工作在做错时会如此影响最终系统&#xff0c;没有别的工作比以后矫正更困难。” —— Fred Brooks 需求难以建立的原因&#x…

CAN模块开发问题概述

问题一 问题描述 工作环境&#xff1a;ECU外接canoe 操作&#xff1a;使用CANoe模拟发送NM报文&#xff0c;然后停发或者断开CANoe 现象&#xff1a;程序跑死&#xff0c;调用call stack查看压栈情况如下图所示 定位代码如下图所示。可见是由于CAN模块在设置Controller状态时…

tomcat--目录结构和文件组成

目录结构 目录说明bin服务启动&#xff0c;停止等相关程序和文件conf配置文件lib库目录logs日志记录webapps应用程序&#xff0c;应用部署目录workjsp编译后的结果文件&#xff0c;建议提前预热访问 /usr/local/apache-tomcat-8.5.100/work/Catalina/localhost/ROOT/org/apac…

[笔试训练](二十二)064:添加字符065:数组变换066:装箱问题

目录 064:添加字符 065:数组变换 066:装箱问题 064:添加字符 添加字符_牛客笔试题_牛客网 (nowcoder.com) 题目&#xff1a; 题解&#xff1a; 枚举所有A&#xff0c;B字符串可能的对应位置&#xff0c;得出对应位置不同字符数量的最小情况 两字符串的字符数量差n-m&…

Hadoop 3.4.0+HBase2.5.8+ZooKeeper3.8.4+Hive+Sqoop 分布式高可用集群部署安装 大数据系列二

创建服务器,参考 虚拟机创建服务器 节点名字节点IP系统版本master11192.168.50.11centos 8.5slave12192.168.50.12centos 8.5slave13192.168.50.13centos 8.5 1 下载组件 Hadoop:官网地址 Hbase:官网地址 ZooKeeper:官网下载 Hive:官网下载 Sqoop:官网下载 为方便同学…

【已解决】力扣打不开

表现&#xff1a; 1.访问国内其他网站都没有问题 2.访问github也能成功 3.wifi没有问题 4.连接同网络的其他主机能打开 唯独力扣打不开&#xff0c;可能是DNS解析错误 》自己网络配置问题 解决办法【亲测可行】 找可用的hosts 打开站长之家&#xff0c;进行DNS查询&#xff…

FreeRTOS事件标志组

目录 一、事件标志组的概念 1、事件标志位 2、事件标志组 二、事件标志组相关API 1、创建事件标志组 2、设置事件标志位 3、清除事件标志位 4、等待事件标志位 三、事件标志组实操 1、实验需求 2、CubeMX配置 3、代码实现 一、事件标志组的概念 1、事件标志位 表…

128.Mit6.S081-实验1-Xv6 and Unix utilities(下)

今天我们继续实验一接下来的内容。 一、pingpong(难度easy) 1.需求 编写一个程序&#xff0c;使用 UNIX 系统调用通过一对管道(每个方向一个管道)在两个进程之间 "ping-pong" 传递一个字节。父进程应该向子进程发送一个字节; 子进程应该打印<pid>: received p…

短视频语音合成:成都鼎茂宏升文化传媒公司

短视频语音合成&#xff1a;技术革新与创意融合的新篇章 随着科技的飞速发展&#xff0c;短视频已经成为人们生活中不可或缺的一部分。在这个快速变化的时代&#xff0c;短视频语音合成技术正逐渐崭露头角&#xff0c;以其独特的魅力和广泛的应用前景&#xff0c;吸引了众多创…