强化学习代码实战入门 | 井字棋Tic-Tac-Toe代码详解

这是一个易理解的 demo,300行左右,可以作为RL的入门代码,辅助基础公式的理解

这个是我自己的学习笔记。三连留下邮箱,可以直接发送完整的代码标注文件~

如有错误,麻烦指出!我已经蛮久没写博了,上一篇RL博客也快一年半了,很久没做这一块了。硕士刚入学,兜兜转转还是回到了RL。欢迎交流~

井字棋规则:感觉就是三子棋。3 * 3棋盘,先连成3子胜利。

代码概览

🌱 类的定义:定义了State、Judger、Player、HumanPlayer四个类,分别代表棋局状态、下棋(裁判)、AI棋手、人类棋手

🌱 状态s:每个棋局是一种状态,使用hash标识唯一的状态。共3^9种状态

🌱 训练过程:首先让2个AI棋手对战,以逐渐完善策略(价值状态函数)。AI棋手训练完后,让AI棋手和人类棋手对战

🌱 训练AI棋手时:初始时,设置胜局状态value为1,输局状态value为0,其余为0.5。然后backup更新,即利用 V(s) ⬅ V(s) + α[V(s')-V(s)] 不断修正value,直到逐渐收敛。α是步长

代码讲解

main主函数

先来看下主函数:

if __name__ == '__main__':train(int(1e5))   # 1e5是浮点型。是epoch  # 2AI对战,完善value functioncompete(int(1e3))   # 2AI对战训练完后,再对战自测胜率 play()   # 人类和AI对战

总之就是先AI对战,再人机大战

State状态类

引入包并定义3*3棋局:

import numpy as np
import pickleBOARD_ROWS = 3
BOARD_COLS = 3
BOARD_SIZE = BOARD_ROWS * BOARD_COLS

再进行State状态类的定义。State类包括函数:

🌻 __init__()初始化、

🌻 hash()计算每个状态的哈希值以索引、

🌻 is_end()检查棋局是否结束、

🌻 next_station()函数将棋手标志放至下一个下棋位置上、

🌻 print()打印当前3*3棋局 

详细注释见代码:

# 每一个state是棋盘的整个状态,共3^9个状态
class State:def __init__(self):# 1 symbol: 先行player# -1 symbol:后行player# 0 symbol:empty positionself.data = np.zeros((BOARD_ROWS, BOARD_COLS))  # 代表boardself.winner = Noneself.hash_val = None    # 使用hash标识每个状态self.end = None# 计算每个状态的哈希值(规则随机)def hash(self):if self.hash_val is None:self.hash_val = 0for i in self.data.reshape(BOARD_COLS * BOARD_ROWS):if i == -1:i = 2self.hash_val = self.hash_val * 3 + ireturn int(self.hash_val)# 检查游戏是否分出胜负,或是平局def is_end(self):if self.end is not None:return self.endresults = []# check rowfor i in range(0, BOARD_ROWS):results.append(np.sum(self.data[i, :]))# check columnsfor i in range(0, BOARD_COLS):results.append(np.sum(self.data[i, :]))# check diagnoalsresults.append(0)for i in range(0, BOARD_ROWS):results[-1] += self.data[i, i]results.append(0)for i in range(0, BOARD_COLS):results[-1] += self.data[i, BOARD_ROWS -1 - i]for result in results:if result == 3:self.end = Trueself.winner = 1return self.endif result == -3:self.end = Trueself.winner = -1return self.end# check tiesum = np.sum(np.abs(self.data))if sum == BOARD_COLS * BOARD_ROWS:self.end = Trueself.winner = 0  # 平局return self.end# 非胜负/平局,继续游戏self.end = Falsereturn self.end# 下一个状态# 将棋手标志放置board位置(i, j)def next_station(self, i, j, symbol):new_state = State()new_state.data = np.copy(self.data)new_state.data[i, j] = symbolreturn new_state# 打印棋局def print(self):for i in range(0, BOARD_ROWS):print('-----------------')out = '| 'for j in range(0, BOARD_COLS):if self.data[i, j] == 1:token = '*'if self.data[i, j] == -1:token = 'x'if self.data[i, j] == 0:token = '0'out += token + ' | 'print(out)print('-----------------')
# 检索当前状态下,所有下一个可能动作带来的状态变换
def get_all_states_impl(current_state, current_symbol, all_states):for i in range(0, BOARD_ROWS):for j in range(0, BOARD_COLS):if current_state.data[i][j] == 0:   # 检索目前所有空格子newState = current_state.next_state(i, j, current_symbol)newHash = newState.hash()if newHash not in all_states.keys():isEnd = newState.is_end()all_states[newHash] = (newState, isEnd)if not isEnd:   # 如果棋手1下完还没结束棋局,棋手2下get_all_states_impl(newState, -current_symbol, all_states)def get_all_states():current_symbol = 1current_state = State()all_states = dict()all_states[current_state.hash()] = (current_state, current_state.is_end())get_all_states_impl(current_state, current_symbol, all_states)return all_states# all_states字典:key是某状态对应的唯一哈希值,value是(state,isEnd)
all_states = get_all_states()

Judger裁判类

Judger类是裁判,其实就是两个棋手轮流下棋。包括函数:

🌻 __init__() 初始化

🌻 reset() 重置

🌻 alternate() 轮流选择下棋手

🌻 play() 双方下棋

详细注释见代码:

class Judger:def __init__(self, player1, player2):self.p1 = player1self.p2 = player2self.current_player = Noneself.p1_symbol = 1self.p2_symbol = -1self.p1.set_symbol(self.p1_symbol)self.p2.set_symbol(self.p2_symbol)def reset(self):self.p1.reset()self.p2.reset()def alternate(self):while True:yield self.p1yield self.p2# play函数用于双方轮流下棋(这个play函数是两个AI棋手下),# act函数用于为当前player选择value最高的下棋位置# @print:if True, print each board during the gamedef play(self, print = False):alternator = self.alternate()self.reset()current_state = State()self.p1.set_state(current_state)self.p1.set_state(current_state)while True: # 一直到棋局结束,return了才结束循环player = next(alternator)   # 双方轮流下棋if print:current_state.print()[i, j, symbol] = player.act()    # 为棋手选择下一步最佳落子next_state_hash = current_state.next_state(i, j, symbol)current_state, is_end = all_states[next_state_hash]self.p1.set_state(current_state)self.p2.set_state(current_state)if is_end:if print:current_state.print()return current_state.winner

Player棋手(AI)类

Player是AI棋手类。包括函数:

🌻 __init__() 初始化

🌻 reset() 重置

🌻 set_state() 设置状态及是否explore

🌻 set_symbol() 状态价值初始化赋值

🌻 backup() 反向更新状态价值:V(s) ⬅ V(s) + α[V(s')-V(s)]

🌻 act() 当前state下,选择最优action

🌻 save_policy() 保存策略(就是estimations价值)

🌻 load_policy() 加载策略

详细注释见代码:

# AI player
# 关于value function(state的函数)解释:https://face2ai.com/RL-RSAB-1-5-An-Extended-Example/
class Player:# @step_size: the step size to update estimation# (好像就是value function),back up更新里的α,详见上方链接解释# @epsilon: the probability to exploredef __init__(self, step_size = 0.1, epsilon = 0.1):self.estimations = dict()self.step_size = step_sizeself.epsilon = epsilonself.states = []self.greedy = []def reset(self):self.states = []self.greedy = []def set_state(self, state):self.states.append(state)self.greedy.append(True)    # 应该是exploit而完全不explore# 这个函数就是给状态state赋value的(estimation字典,key为hash(对应某个状态),值为value),# 在棋局结束状态下,如果这个状态赢,赋1;若平局,赋0.5,输则赋0# 正在进行时的棋局状态一律赋0.5def set_symbol(self, symbol):self.symbol = symbolfor hash_val in all_states.keys():(state, is_end) = all_states[hash_val]if is_end:if state.winner == self.symbol:self.estimations[hash_val] = 1.0elif state.winner == 0:self.estimations[hash_val] = 0.5else:self.estimations[hash_val] = 0else:self.estimations[hash_val] = 0.5# 反向更新价值状态函数(value estimation)def backup(self):self.states = [state.hash() for state in self.states]# 反向更新:V(s) ⬅ V(s) + α[V(s')-V(s)]# 可参考链接:https://face2ai.com/RL-RSAB-1-5-An-Extended-Example/for i in reversed(range(len(self.states) - 1)):state = self.states[i]td_error = self.greedy[i] * (self.estimations[self.states[i + 1]] - self.estimations[state])self.estimations[state] += self.step_size * td_error# 当前state下,选择下一步的最优action# act函数的返回结果为下棋位置和棋手标志(i, j, symbol)def act(self):state = self.states[-1]next_states = []next_positions = []# 找出目前state下所有空位for i in range(BOARD_ROWS):for j in range(BOARD_COLS):if state.data[i, j] == 0:next_positions.append([i,j])next_states.append(state.next_state(i, j, self.symbol).hash())# exploreif np.random.rand() < self.epsilon:    # 随机生成(0,1)之间数 action = next_positions[np.random.randint(len(next_positions))]action.append(self.symbol)self.greedy[-1] = Falsereturn action# 否则exploitvalues = []for hash, pos in zip(next_states, next_positions):values.append((self.estimations[hash], pos))np.random.shuffle(values)values.sort(key = lambda x:x[0], reverse = True)    # 按照state的value值大小倒序排action = values[0][1]    # 选择value值最大的action的位置action.append(self.symbol)  # 为这个动作加上棋手标志return actiondef save_policy(self):# bin是二进制格式的文件with open('policy_%s.bin' % ('first' if self.symbol == 1 else 'second'), 'wb') as f:pickle.dump(self.estimations, f)    # 对象存储def load_policy(self):with open('policy_%s.bin' % ('first' if self.symbol == 1 else 'second'), 'rb') as f:self.estimations = pickle.load(f)

HumanPlayer棋手(人类)类

HumanPlayer类就是人类棋手。包括函数:

🌻 __init__() 初始化

🌻 set_state() 设置状态

🌻 set_symbol() 设置棋手标志

🌻 act() 人类棋手通过键盘下棋

详细注释见代码:

# human interface
# input a number to put a chessman
# | q | w | e |
# | a | s | d |
# | z | x | c |
class HumanPlayer:def __init__(self, **kwargs):self.symbol = Noneself.keys = ['q', 'w', 'e', 'a', 's', 'd', 'z', 'x', 'c']self.state = Nonereturndef reset(self):returndef set_state(self, state):self.state = statedef set_symbol(self, symbol):self.symbol = symbolreturndef backup(self, _):returndef act(self):self.state.print()key = input("Input your position:")  # 将这句话显示在屏幕上,接收用户输入的值,赋给key# 默认用户的输入是键盘最左边三行三列字母data = self.keys.index(key)  # 索引i = data // int(BOARD_COLS)j = data % BOARD_COLSreturn (i, j, self.symbol)

train函数

train()其实就是让两个AI对战,不断完善value function,直至逐渐收敛。是在训练AI棋手

# 2个AI间训练
# 两个AI player打,不断完善value function(即estimations)
# 训练结束后, Epoch 10000, player 1 win 0.08, player 2 win 0.03
def train(epochs):player1 = Player(epsilon = 0.01)player2 = Player(epsilon = 0.01)judger = Judger(player1, player2)player1_win = 0.0player2_win = 0.0for i in range(1, epochs + 1):winner = judger.play(print = False)   # 开始下棋if winner == 1:player1_win += 1if winner == -1:player2_win += 1print('Epoch %d, player1 win %.02f, player2 win %.02f' % (i, player1_win / i, player2_win / i))player1.backup()player2.backup()judger.reset()player1.save_policy()player2.save_policy()

compete函数

相当于train后的test。这个游戏规则太简单了,测试时两个AI都是平局

# 2个AI间测试
# 训练结束后的测试,AI之间就没有输赢了,全是平局:
# 1000 turns, player 1 win 0.00, player 2 win 0.00
# 计算下turns次棋,两个AI棋手的分别胜率
def compete(turns):player1 = Player(epsilon = 0)player2 = Player(epsilon = 0)judger = Judger(player1, player2)player1.load_policy()player2.load_policy()player1_win = 0.0player2_win = 0.0for i in range(0, turns):winner  = judger.play()if winner == 1:player1_vin += 1if winner == -1:player2_win += 1judger.reset()print('%d turns, player1 win %.02f, player1 win %.02f' % (turns, player1_win / turns, player2_win / turns))

play函数

人类和AI下棋

# 人类和AI下棋
def play():while True:player1 = HumanPlayer()player2 = Player(epsilon = 0)judger = Judger(player1, player2)player2.load_policy()   # AI棋手使用之前两个AI对战储存的policy(即value function,因为上面设置的epsilon为0,直接贪婪地选造成value最大状态的action)winner = judger.play()if winner == player2.symbol:print("You lose!")elif winner == player1.symbol:print("You win!")else:print("It is a tie!")

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

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

相关文章

layui中多个checkbox只能进行单选且一个被选中则取消其他的选中状态

在layui中&#xff0c;要实现多个checkbox只能进行单选&#xff0c;如果一个被选中&#xff0c;则取消其他的选中状态&#xff0c;可以通过以下步骤实现&#xff1a; 在HTML中&#xff0c;为每个checkbox元素添加相同的class属性&#xff0c;例如"my-checkbox"。 &…

兵者多诡(HCTF2016)

环境:https://github.com/MartinxMax/CTFer_Zero_one 题目简介 解题过程 登录首页 提交png图片上传抓包&#xff0c;可以看到是向upload文件提交数据 在fp参数中尝试伪协议读取home.php文件 http://127.0.0.1:88/HCTF2016-LFI/home.php?fpphp://filter/readconvert.base64…

Mybatis的关系关联配置

前言 MyBatis是一个流行的Java持久化框架&#xff0c;它提供了一种简单而强大的方式来映射Java对象和关系数据库之间的数据。在MyBatis中&#xff0c;关系关联配置是一种用于定义对象之间关系的方式&#xff0c;它允许我们在查询数据库时同时获取相关联的对象。 在MyBatis中&…

第17章 站点构建

mini商城第17章 站点构建 一、课题 站点构建 二、回顾 1、Gateway限流 2、Nginx限流 3、Redis集群应用 4、缓存灾难处理 三、目标 1、Sentinel Sentinel介绍 Sentinel核心功能 Sentinel集成Gateway Sentinel控制台 2、Lvs+Nginx集群 Lvs负载均衡模式 NAT模式 TUN模式 …

h5 uniapp如何保存照片【saveImageToPhotosAlbum不支持h5端】

报错&#xff1a;chunk-vendors.7e18ac5d.js:1 [system] API saveImageToPhotosAlbum is not yet h5 uniapp如何保存照片 //调用预览图片的方法uni.previewImage({urls: [img],current: 0, //点击图片传过来的下标success: (res) > {console.log(预览图片成功);// uni.showT…

实现在外网SSH远程访问内网树莓派的详细教程

文章目录 如何在局域网外SSH远程访问连接到家里的树莓派&#xff1f;如何通过 SSH 连接到树莓派步骤1. 在 Raspberry Pi 上启用 SSH步骤2. 查找树莓派的 IP 地址步骤3. SSH 到你的树莓派步骤 4. 在任何地点访问家中的树莓派4.1 安装 Cpolar4.2 cpolar进行token认证4.3 配置cpol…

ubuntu 20.04 docker安装emqx 最新版本或指定版本

要在Ubuntu 20.04上使用Docker安装EMQX&#xff08;EMQ X Broker&#xff09;的4.4.3版本&#xff0c;您可以执行以下步骤&#xff1a; 1.更新系统包列表&#xff1a; sudo apt update2.安装Docker&#xff1a; sudo apt install docker.io3.启动Docker服务并设置其开机自启…

HCIA自学笔记01-冲突域

共享式网络&#xff08;用同一根同轴电缆通信&#xff09;中可能会出现信号冲突现象。 如图是一个10BASE5以太网&#xff0c;每个主机都是用同一根同轴电缆来与其它主机进行通信&#xff0c;因此&#xff0c;这里的同轴电缆又被称为共享介质&#xff0c;相应的网络被称为共享介…

15:00面试,15:06就出来了,问的问题有点变态。。。

从小厂出来&#xff0c;没想到在另一家公司又寄了。 到这家公司开始上班&#xff0c;加班是每天必不可少的&#xff0c;看在钱给的比较多的份上&#xff0c;就不太计较了。没想到8月一纸通知&#xff0c;所有人不准加班&#xff0c;加班费不仅没有了&#xff0c;薪资还要降40%,…

算法通关村第12关【白银】| 字符串经典问题

一、反转问题 1.反转字符串 思路&#xff1a;双指针&#xff0c;反转数组一个套路 class Solution {public void reverseString(char[] s) {int l 0;int r s.length -1;while(l<r){char c s[l];s[l] s[r];s[r] c;l;r--;}} } 2.k个一组反转 思路&#xff1a;每k个进行…

第14章_瑞萨MCU零基础入门系列教程之QSPI

本教程基于韦东山百问网出的 DShanMCU-RA6M5开发板 进行编写&#xff0c;需要的同学可以在这里获取&#xff1a; https://item.taobao.com/item.htm?id728461040949 配套资料获取&#xff1a;https://renesas-docs.100ask.net 瑞萨MCU零基础入门系列教程汇总&#xff1a; ht…

【leetcode 力扣刷题】删除字符串中的子串or字符以满足要求

删除字符串中的子串或者字符以满足题意要求 1234. 替换子串得到平衡字符串680. 验证回文串917. 仅仅反转字母 1234. 替换子串得到平衡字符串 题目链接&#xff1a;1234. 替换子串得到平衡字符串 题目内容&#xff1a; 题目中给出了平衡字符串的定义——只有’Q’&#xff0c;…

彻底掌握Protobuf编码原理与实战

目录 1.类型2.VARINT 2.1 无符号数2.2 有符号数3.定长 3.1 I64类型3.2 I32类型4.LEN5.代码 学习这些有什么用&#xff1f; - 如果你是后端开发者&#xff0c;掌握这个对工作非常有用 - 如果你是求职者&#xff0c;面试时可以临危不惧 1.类型 最近看到有直接操作wire type相关的…

React+antd实现可编辑单元格,非官网写法,不使用可编辑行和form验证

antd3以上的写法乍一看还挺复杂&#xff0c;自己写了个精简版 没用EditableRowCell的结构&#xff0c;也不使用Context、高阶组件等&#xff0c;不使用form验证 最终效果&#xff1a; class EditableCell extends React.Component {state {editing: false};toggleEdit () &…

系统软件启动过程

实验一&#xff1a;系统软件启动过程 参考 重要文件 调用顺序 1. boot/bootasm.S | bootasm.asm&#xff08;修改了名字&#xff0c;以便于彩色显示&#xff09;a. 开启A20 16位地址线 实现 20位地址访问 芯片版本兼容通过写 键盘控制器8042 的 64h端口 与 60h端口。b.…

Selenium自动化测试框架常见异常分析及解决方法

01 pycharm中导入selenium报错 现象: pycharm中输入from selenium import webdriver, selenium标红 原因1: pycharm使用的虚拟环境中没有安装selenium, 解决方法: 在pycharm中通过设置或terminal面板重新安装selenium 原因2: 当前项目下有selenium.py,和系统包名冲突导致, …

C++ concept的概念和使用

C concept的概念和使用 concept 这套语法优化了模板编程&#xff0c;替代了原来的SFINAE编程模式&#xff0c;通过给模板类参数加入限制条件&#xff0c;使得代码可读性更强、编译更快、错误提示更容易理解。 SFINAE编程模式 SFINAE 是"Substitution Failure Is Not An…

msql 批量更新生成不同的uuid()

有时需要对表里的批量数据设置主键uuid&#xff0c;要求每条数据的uuid都不一样。 一、方法&#xff1a; UPDATE honghang_1month_list SET idUUID();UPDATE honghang_1month_list SET idREPLACE(id, -, );注意不能直接执行UPDATE honghang_1month_list SET idREPLACE(UUID()…

Amazon Aurora MySQL 和 Amazon RDS for MySQL 集群故障转移和只读实例扩容时间测试

01 测试背景 Amazon Aurora MySQL 是与 MySQL 兼容的关系数据库&#xff0c;专为云而打造&#xff0c;性能和可用性与商用数据库相当&#xff0c;成本只有其 1/10。 Amazon RDS for MySQL 让您能够在云中更轻松设置、操作和扩展 MySQL 部署。借助 Amazon RDS&#xff0c;您可以…

Android 13.0 Launcher3定制之双层改单层(去掉抽屉式二)

1.概述 在13.0的系统产品开发中,对于在Launcher3中的抽屉模式也就是双层模式,在系统原生的Launcher3中就是双层抽屉模式的, 但是在通过抽屉上滑的模式拉出app列表页,但是在一些产品开发中,对于单层模式的Launcher3的产品模式也是常用的功能, 所以需要了解抽屉模式,然后修…