From 0ffecb2c2e9b15c9ab556959a539e551ac33969d Mon Sep 17 00:00:00 2001 From: ooo oo <106524776+ooooo-create@users.noreply.github.com> Date: Sun, 22 Oct 2023 21:54:05 +0800 Subject: [PATCH] [CodeStyle] upgrade `Lucas-C/pre-commit-hooks` in `.pre-commit-config.yaml` (#6240) --- ...al_distributed_DataParallel_docs-issue.yml | 32 +- .../4_eval_distributed_PS_docs-issue.yml | 24 +- .pre-commit-config.yaml | 10 +- ci_scripts/gendoc.sh | 12 +- .../mkldnn/acquire_api/scripts/acquire.dot | 16 +- ...\200\220Hackathon No.111\343\200\221PR.md" | 233 +- ...3\200\220Hackathon No.69\343\200\221PR.md" | 330 +-- docs/guides/cinn/basic_operator_cn.md | 10 +- .../reinforcement_learning/AlphaZero.ipynb | 2192 ++++++++--------- docs/templates/layout.html | 2 +- 10 files changed, 1428 insertions(+), 1433 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/3_eval_distributed_DataParallel_docs-issue.yml b/.github/ISSUE_TEMPLATE/3_eval_distributed_DataParallel_docs-issue.yml index 2c73df1fdc5..69df305a475 100644 --- a/.github/ISSUE_TEMPLATE/3_eval_distributed_DataParallel_docs-issue.yml +++ b/.github/ISSUE_TEMPLATE/3_eval_distributed_DataParallel_docs-issue.yml @@ -60,7 +60,7 @@ body: attributes: label: 1.导入分布式训练需要的依赖包 description: | - 针对 导入分布式训练需要的依赖 这一环节,请回答以下内容 + 针对「导入分布式训练需要的依赖」这一环节,请回答以下内容 value: | - 完成情况(成功/不成功) : - 遇到问题: @@ -74,7 +74,7 @@ body: attributes: label: 2.初始化分布式训练环境 description: | - 针对 初始化分布式训练环境 这一环节,请回答以下内容 + 针对「初始化分布式训练环境」这一环节,请回答以下内容 value: | - 完成情况(成功/不成功) : - 遇到问题: @@ -88,7 +88,7 @@ body: attributes: label: 3.设置分布式训练需要的优化器 description: | - 针对 设置分布式训练需要的优化器 这一环节,请回答以下内容 + 针对「设置分布式训练需要的优化器」这一环节,请回答以下内容 value: | - 完成情况(成功/不成功) : - 遇到问题: @@ -103,7 +103,7 @@ body: attributes: label: 4.数据集拆分 description: | - 针对 数据集拆分 这一环节,请回答以下内容 + 针对「数据集拆分」这一环节,请回答以下内容 value: | - 完成情况(成功/不成功) : - 遇到问题: @@ -115,9 +115,9 @@ body: - type: textarea id: distributed_dp_eval5 attributes: - label: 5.构建训练代码 + label: 5.构建训练代码 description: | - 针对 构建训练代码 这一环节,请回答以下内容 + 针对「构建训练代码」这一环节,请回答以下内容 value: | - 完成情况(成功/不成功) : - 遇到问题: @@ -131,7 +131,7 @@ body: attributes: label: 6.单机多卡分布式训练 description: | - 针对 单机多卡分布式训练 这一环节,请回答以下内容 + 针对「单机多卡分布式训练」这一环节,请回答以下内容 value: | - 完成情况(成功/不成功) : - 遇到问题: @@ -145,7 +145,7 @@ body: attributes: label: 7.多机多卡分布式训练 description: | - 针对 多机多卡分布式训练 这一环节,请回答以下内容 + 针对「多机多卡分布式训练」这一环节,请回答以下内容 value: | - 完成情况(成功/不成功) : - 遇到问题: @@ -197,7 +197,7 @@ body: attributes: label: 1.导入分布式训练需要的依赖包 description: | - 针对 导入分布式训练需要的依赖 这一环节,请回答以下内容 + 针对「导入分布式训练需要的依赖」这一环节,请回答以下内容 value: | - 完成情况(成功/不成功) : - 遇到问题: @@ -211,7 +211,7 @@ body: attributes: label: 2.初始化分布式训练环境 description: | - 针对 初始化分布式训练环境 这一环节,请回答以下内容 + 针对「初始化分布式训练环境」这一环节,请回答以下内容 value: | - 完成情况(成功/不成功) : - 遇到问题: @@ -225,7 +225,7 @@ body: attributes: label: 3.设置分布式训练需要的优化器 description: | - 针对 设置分布式训练需要的优化器 这一环节,请回答以下内容 + 针对「设置分布式训练需要的优化器」这一环节,请回答以下内容 value: | - 完成情况(成功/不成功) : - 遇到问题: @@ -240,7 +240,7 @@ body: attributes: label: 4.数据集拆分 description: | - 针对 数据集拆分 这一环节,请回答以下内容 + 针对「数据集拆分」这一环节,请回答以下内容 value: | - 完成情况(成功/不成功) : - 遇到问题: @@ -252,9 +252,9 @@ body: - type: textarea id: distributed_dp_eval15 attributes: - label: 5.构建训练代码 + label: 5.构建训练代码 description: | - 针对 构建训练代码 这一环节,请回答以下内容 + 针对「构建训练代码」这一环节,请回答以下内容 value: | - 完成情况(成功/不成功) : - 遇到问题: @@ -268,7 +268,7 @@ body: attributes: label: 6.单机多卡分布式训练 description: | - 针对 单机多卡分布式训练 这一环节,请回答以下内容 + 针对「单机多卡分布式训练」这一环节,请回答以下内容 value: | - 完成情况(成功/不成功) : - 遇到问题: @@ -282,7 +282,7 @@ body: attributes: label: 7.多机多卡分布式训练 description: | - 针对 多机多卡分布式训练 这一环节,请回答以下内容 + 针对「多机多卡分布式训练」这一环节,请回答以下内容 value: | - 完成情况(成功/不成功) : - 遇到问题: diff --git a/.github/ISSUE_TEMPLATE/4_eval_distributed_PS_docs-issue.yml b/.github/ISSUE_TEMPLATE/4_eval_distributed_PS_docs-issue.yml index 77a0dad1bb4..05f47036f12 100644 --- a/.github/ISSUE_TEMPLATE/4_eval_distributed_PS_docs-issue.yml +++ b/.github/ISSUE_TEMPLATE/4_eval_distributed_PS_docs-issue.yml @@ -49,7 +49,7 @@ body: attributes: label: 1.导入分布式训练需要的依赖包 description: | - 针对 导入分布式训练需要的依赖 这一环节,请回答以下内容 + 针对「导入分布式训练需要的依赖」这一环节,请回答以下内容 value: | - 完成情况(成功/不成功) : - 遇到问题: @@ -63,7 +63,7 @@ body: attributes: label: 2.定义分布式模式并初始化分布式训练环境 description: | - 针对 定义分布式模式并初始化分布式训练环境 这一环节,请回答以下内容 + 针对「定义分布式模式并初始化分布式训练环境」这一环节,请回答以下内容 value: | - 完成情况(成功/不成功) : - 遇到问题: @@ -77,7 +77,7 @@ body: attributes: label: 3.加载模型 description: | - 针对 加载模型 这一环节,请回答以下内容 + 针对「加载模型」这一环节,请回答以下内容 value: | - 完成情况(成功/不成功) : - 遇到问题: @@ -92,7 +92,7 @@ body: attributes: label: 4.构建 dataset 加载数据 description: | - 针对 构建 dataset 加载数据 这一环节,请回答以下内容 + 针对「构建 dataset 加载数据」这一环节,请回答以下内容 value: | - 完成情况(成功/不成功) : - 遇到问题: @@ -106,7 +106,7 @@ body: attributes: label: 5.定义参数更新策略及优化器 description: | - 针对 定义参数更新策略及优化器 这一环节,请回答以下内容 + 针对「定义参数更新策略及优化器」这一环节,请回答以下内容 value: | - 完成情况(成功/不成功) : - 遇到问题: @@ -120,7 +120,7 @@ body: attributes: label: 6.开始多机分布式训练 description: | - 针对 开始多机分布式训练 这一环节,请回答以下内容 + 针对「开始多机分布式训练」这一环节,请回答以下内容 value: | - 完成情况(成功/不成功) : - 遇到问题: @@ -173,7 +173,7 @@ body: attributes: label: 1.导入分布式训练需要的依赖包 description: | - 针对 导入分布式训练需要的依赖 这一环节,请回答以下内容 + 针对「导入分布式训练需要的依赖」这一环节,请回答以下内容 value: | - 完成情况(成功/不成功) : - 遇到问题: @@ -187,7 +187,7 @@ body: attributes: label: 2.定义分布式模式并初始化分布式训练环境 description: | - 针对 定义分布式模式并初始化分布式训练环境 这一环节,请回答以下内容 + 针对「定义分布式模式并初始化分布式训练环境」这一环节,请回答以下内容 value: | - 完成情况(成功/不成功) : - 遇到问题: @@ -201,7 +201,7 @@ body: attributes: label: 3.加载模型 description: | - 针对 加载模型 这一环节,请回答以下内容 + 针对「加载模型」这一环节,请回答以下内容 value: | - 完成情况(成功/不成功) : - 遇到问题: @@ -216,7 +216,7 @@ body: attributes: label: 4.构建 dataset 加载数据 description: | - 针对 构建 dataset 加载数据 这一环节,请回答以下内容 + 针对「构建 dataset 加载数据」这一环节,请回答以下内容 value: | - 完成情况(成功/不成功) : - 遇到问题: @@ -230,7 +230,7 @@ body: attributes: label: 5.定义参数更新策略及优化器 description: | - 针对 定义参数更新策略及优化器 这一环节,请回答以下内容 + 针对「定义参数更新策略及优化器」这一环节,请回答以下内容 value: | - 完成情况(成功/不成功) : - 遇到问题: @@ -244,7 +244,7 @@ body: attributes: label: 6.开始多机分布式训练 description: | - 针对 开始多机分布式训练 这一环节,请回答以下内容 + 针对「开始多机分布式训练」这一环节,请回答以下内容 value: | - 完成情况(成功/不成功) : - 遇到问题: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c2aa71a7d0a..a018d2f2b27 100755 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,16 +16,12 @@ repos: - id: trailing-whitespace files: \.md$|\.rst$ - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: v1.1.14 + rev: v1.5.1 hooks: - - id: forbid-crlf - files: \.md$|\.rst$ - id: remove-crlf - files: \.md$|\.rst$ - - id: forbid-tabs - files: \.md$|\.rst$ + types: [text] - id: remove-tabs - files: \.md$|\.rst$ + types: [text] - repo: https://github.com/ShigureLab/dochooks rev: v0.3.0 hooks: diff --git a/ci_scripts/gendoc.sh b/ci_scripts/gendoc.sh index ceb33074311..79c9fdf04f3 100755 --- a/ci_scripts/gendoc.sh +++ b/ci_scripts/gendoc.sh @@ -32,10 +32,10 @@ if [ -f ${FLUIDDOCDIR}/ci_scripts/hooks/pre-doc-compile.sh ] ; then fi thread=2 -tmp_fifofile=/tmp/$$.fifo #脚本运行的当前进程ID号作为文件名 -mkfifo $tmp_fifofile #新建一个随机fifo管道文件 -exec 6<>$tmp_fifofile #定义文件描述符6指向这个fifo管道文件 -rm $tmp_fifofile #清空管道内容 +tmp_fifofile=/tmp/$$.fifo # 脚本运行的当前进程ID号作为文件名 +mkfifo $tmp_fifofile # 新建一个随机fifo管道文件 +exec 6<>$tmp_fifofile # 定义文件描述符6指向这个fifo管道文件 +rm $tmp_fifofile # 清空管道内容 # for循环 往 fifo管道文件中写入$thread个空行 for ((i=0;i<$thread;i++));do @@ -68,8 +68,8 @@ for lang in en zh ; do } & done -wait #等到后台的进程都执行完毕 -exec 6>&- ##删除文件描述符6 +wait # 等到后台的进程都执行完毕 +exec 6>&- # 删除文件描述符6 if [ -f ${FLUIDDOCDIR}/ci_scripts/hooks/post-doc-compile.sh ] ; then ${FLUIDDOCDIR}/ci_scripts/hooks/post-doc-compile.sh ${OUTPUTDIR} ${VERSIONSTR} diff --git a/docs/design/mkldnn/acquire_api/scripts/acquire.dot b/docs/design/mkldnn/acquire_api/scripts/acquire.dot index 3332fc6eb3d..9bbcf939cf8 100644 --- a/docs/design/mkldnn/acquire_api/scripts/acquire.dot +++ b/docs/design/mkldnn/acquire_api/scripts/acquire.dot @@ -8,27 +8,27 @@ nodesep=1 node[width=4.4,shape=box] - Node0x490c380 [shape=record,label="SoftmaxMKLDNNKernel::Compute()\l"]; + Node0x490c380 [shape=record,label="SoftmaxMKLDNNKernel::Compute()\l"]; - Node0x4ab38f0 [shape=record,label="MKLDNNActivationKernel::Compute()\l"]; + Node0x4ab38f0 [shape=record,label="MKLDNNActivationKernel::Compute()\l"]; subgraph cluster_A { label="Derived Handlers" node[width=7.4,shape=box] style=dotted // Dummy[shape=record,label="", color=invis]; - Node0x4915e90 [shape=record,label="SoftmaxMKLDNNHandler::SoftmaxMKLDNNHandler\()\l"]; - Node0x4b2e4f0 [shape=record,label="ActivationMKLDNNHandler::ActivationMKLDNNHandler\()\l"]; + Node0x4915e90 [shape=record,label="SoftmaxMKLDNNHandler::SoftmaxMKLDNNHandler\()\l"]; + Node0x4b2e4f0 [shape=record,label="ActivationMKLDNNHandler::ActivationMKLDNNHandler\()\l"]; } subgraph cluster_B { label="Base MKLDNNHandler" style=dotted node[width=6.2,shape=box] - Node0x49164c0 [shape=record,label="MKLDNNHandlerT::AcquireSrcMemory()\l"]; - Dst[shape=record,label="MKLDNNHandlerT::AcquireDstMemory()\l"]; - Node0x491bca0 [shape=record,label="MKLDNNHandlerT::AcquireForwardPrimitive()\l"]; - Node0x496cfc0 [shape=record,label="MKLDNNHandlerT::AcquireForwardPrimitiveDescriptor()\l"]; + Node0x49164c0 [shape=record,label="MKLDNNHandlerT::AcquireSrcMemory()\l"]; + Dst[shape=record,label="MKLDNNHandlerT::AcquireDstMemory()\l"]; + Node0x491bca0 [shape=record,label="MKLDNNHandlerT::AcquireForwardPrimitive()\l"]; + Node0x496cfc0 [shape=record,label="MKLDNNHandlerT::AcquireForwardPrimitiveDescriptor()\l"]; } Node0x490c380 -> Node0x4915e90[style="bold"]; diff --git "a/docs/eval/\343\200\220Hackathon No.111\343\200\221PR.md" "b/docs/eval/\343\200\220Hackathon No.111\343\200\221PR.md" index 8266f01d60a..b5ef9dbf0c7 100644 --- "a/docs/eval/\343\200\220Hackathon No.111\343\200\221PR.md" +++ "b/docs/eval/\343\200\220Hackathon No.111\343\200\221PR.md" @@ -2,82 +2,82 @@ | 领域 |飞桨动静转换评估 | | --- | --- | -|提交作者 | 王源 袁闯闯 | -|提交时间 | 2022-05-04 | -|版本号 | V1.0 | -|依赖飞桨版本 | V2.2 | -|文件名 | 【Hackathon No.111】PR.md | +|提交作者 | 王源 袁闯闯 | +|提交时间 | 2022-05-04 | +|版本号 | V1.0 | +|依赖飞桨版本 | V2.2 | +|文件名 | 【Hackathon No.111】PR.md | -一个完整的使用动静转换@to_static导出、可部署的模型完整代码(参考以图搜图) +一个完整的使用动静转换@to_static 导出、可部署的模型完整代码(参考以图搜图) -以下为 AI Studio 任务链接 (cifar10动态图转为静态图(完整demo)) +以下为 AI Studio 任务链接 (cifar10 动态图转为静态图(完整 demo)) AI Studio 任务链接:https://aistudio.baidu.com/aistudio/projectdetail/3937674?channel=0&channelType=0&shared=1 # 1、任务描述: - 飞桨框架于 2.0 正式版之后正式发布了动静转换@to_static功能,并在2.1、2.2 两个大版本中不断新增了各项功能,以及详细的使用文档和最佳实践教程(以图搜图)。 - - 在本任务中,我们希望你全面体验飞桨的动静转换@to_static功能,即参考飞桨官网 -> 使用指南 -> 动态图转静态 下的内容,体验动转静模型导出、动转静训练等功能, - - 产出一份整体功能体验报告。 + 飞桨框架于 2.0 正式版之后正式发布了动静转换@to_static 功能,并在 2.1、2.2 两个大版本中不断新增了各项功能,以及详细的使用文档和最佳实践教程(以图搜图)。 + + 在本任务中,我们希望你全面体验飞桨的动静转换@to_static 功能,即参考飞桨官网 -> 使用指南 -> 动态图转静态 下的内容,体验动转静模型导出、动转静训练等功能, + + 产出一份整体功能体验报告。 # 2、环境配置: - 因为需要体验飞桨paddlepaddle框架的动转静模型导出、动转静训练等功能,所以首先需要安装飞桨paddlepaddle框架,运行环境使用pycharm和anaconda。 - - 所以在进行PaddlePaddle安装之前应确保Anaconda软件环境已经正确安装。软件下载和安装参见Anaconda官网(https://www.anaconda.com/)。 - - 在已经正确安装Anaconda的情况下请按照下列步骤安装PaddlePaddle。 + 因为需要体验飞桨 paddlepaddle 框架的动转静模型导出、动转静训练等功能,所以首先需要安装飞桨 paddlepaddle 框架,运行环境使用 pycharm 和 anaconda。 + + 所以在进行 PaddlePaddle 安装之前应确保 Anaconda 软件环境已经正确安装。软件下载和安装参见 Anaconda 官网(https://www.anaconda.com/)。 + + 在已经正确安装 Anaconda 的情况下请按照下列步骤安装 PaddlePaddle。 - -Windows 7/8/10 专业版/企业版 (64bit) + -Windows 7/8/10 专业版/企业版 (64bit) - -GPU版本支持CUDA 10.1/10.2/11.2,且仅支持单卡 + -GPU 版本支持 CUDA 10.1/10.2/11.2,且仅支持单卡 - -conda 版本 4.8.3+ (64 bit) + -conda 版本 4.8.3+ (64 bit) ## 2.1、创建虚拟环境: - 1、安装环境 - 首先根据具体的Python版本创建Anaconda虚拟环境,PaddlePaddle的Anaconda安装支持以下四种Python安装环境。 -如果您想使用的python版本为3.6: + 首先根据具体的 Python 版本创建 Anaconda 虚拟环境,PaddlePaddle 的 Anaconda 安装支持以下四种 Python 安装环境。 +如果您想使用的 python 版本为 3.6: ```python conda create -n paddle_env python=3.6 ``` -如果您想使用的python版本为3.7: +如果您想使用的 python 版本为 3.7: ```python conda create -n paddle_env python=3.7 -``` -如果您想使用的python版本为3.8: +``` +如果您想使用的 python 版本为 3.8: ```python conda create -n paddle_env python=3.8 ``` -如果您想使用的python版本为3.9: +如果您想使用的 python 版本为 3.9: ```python conda create -n paddle_env python=3.9 -``` -本实验使用python版本为3.9,虚拟环境命名为paddlepaddle-gpu,即: +``` +本实验使用 python 版本为 3.9,虚拟环境命名为 paddlepaddle-gpu,即: ```python conda create -n paddlepaddle-gpu python=3.9 -``` -- 2、进入Anaconda虚拟环境 +``` +- 2、进入 Anaconda 虚拟环境 ```python activate paddlepaddle-gpu ``` - 3、开始安装 -GPU版的PaddlePaddle -本实验为 CUDA 10.2,需要搭配cuDNN 7 (cuDNN>=7.6.5, 多卡环境下 NCCL>=2.7) -添加清华源(可选),对于国内用户无法连接到Anaconda官方源的可以按照以下命令添加清华源: +GPU 版的 PaddlePaddle +本实验为 CUDA 10.2,需要搭配 cuDNN 7 (cuDNN>=7.6.5, 多卡环境下 NCCL>=2.7) +添加清华源(可选),对于国内用户无法连接到 Anaconda 官方源的可以按照以下命令添加清华源: ```python conda install paddlepaddle-gpu==2.2.2 cudatoolkit=10.2 --channel https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/Paddle/ ``` -您可参考NVIDIA官方文档了解CUDA和CUDNN的安装流程和配置方法。 +您可参考 NVIDIA 官方文档了解 CUDA 和 CUDNN 的安装流程和配置方法。 - 4、验证安装 -安装完成后您可以使用 python 或 python3 进入python解释器,输入import paddle ,再输入 paddle.utils.run_check() +安装完成后您可以使用 python 或 python3 进入 python 解释器,输入 import paddle ,再输入 paddle.utils.run_check() -如果出现PaddlePaddle is installed successfully!,说明您已成功安装。 +如果出现 PaddlePaddle is installed successfully!,说明您已成功安装。 -## 2.2、paddlepaddle环境配置: +## 2.2、paddlepaddle 环境配置: ```python import paddle import paddle.nn.functional as F @@ -99,7 +99,7 @@ print(paddle.__version__) 本示例采用 CIFAR-10 数据集。 -CIFAR-10 和 CIFAR-100 是8000 万个微小图像数据集的标记子集。它们由 Alex Krizhevsky、Vinod Nair 和 Geoffrey Hinton 收集。 +CIFAR-10 和 CIFAR-100 是 8000 万个微小图像数据集的标记子集。它们由 Alex Krizhevsky、Vinod Nair 和 Geoffrey Hinton 收集。 数据集分为五个训练批次和一个测试批次,每个批次有 10000 张图像。测试批次恰好包含来自每个类别的 1000 个随机选择的图像。训练批次包含随机顺序的剩余图像, @@ -125,7 +125,7 @@ y_train = np.zeros((50000, 1), dtype='int32') for i in range(len(cifar10_train)): train_image, train_label = cifar10_train[i] - + # normalize the data x_train[i,:, :, :] = train_image / 255. y_train[i, 0] = train_label @@ -138,7 +138,7 @@ y_test = np.zeros((10000, 1), dtype='int64') for i in range(len(cifar10_test)): test_image, test_label = cifar10_test[i] - + # normalize the data x_test[i,:, :, :] = test_image / 255. y_test[i, 0] = test_label @@ -175,7 +175,7 @@ show_collage(examples) 图片检索的模型的训练样本跟常见的分类任务的训练样本不太一样的地方在于,每个训练样本并不是一个(image, class)这样的形式。而是(image0, image1, similary_or_not)的形式,即,每 -一个训练样本由两张图片组成,而其label是这两张图片是否相似的标志位(0或者1)。 +一个训练样本由两张图片组成,而其 label 是这两张图片是否相似的标志位(0 或者 1)。 很自然的能够想到,来自同一个类别的两张图片,是相似的图片,而来自不同类别的两张图片,应该是不相似的图片。 @@ -218,31 +218,31 @@ pairs_train_reader = anchor_positive_pairs(num_batchs=1000) ``` # 4、模型组网: 把图片转换为高维的向量表示的网络 -目标是首先把图片转换为高维空间的表示,然后计算图片在高维空间表示时的相似度。 下面的网络结构用来把一个形状为(3, 32, 32)的图片转换成形状为(8,)的向量。在有些资料中也会把这个转换成的向量称为Embedding,请注意,这与自然语言处理领域的词向量的区别。 下面的模型由三个连续的卷积加一个全局均值池化,然后用一个线性全链接层映射到维数为8的向量空间。为了后续计算余弦相似度时的便利,还在最后做了归一化。(即,余弦相似度的分母部分) +目标是首先把图片转换为高维空间的表示,然后计算图片在高维空间表示时的相似度。 下面的网络结构用来把一个形状为(3, 32, 32)的图片转换成形状为(8,)的向量。在有些资料中也会把这个转换成的向量称为 Embedding,请注意,这与自然语言处理领域的词向量的区别。 下面的模型由三个连续的卷积加一个全局均值池化,然后用一个线性全链接层映射到维数为 8 的向量空间。为了后续计算余弦相似度时的便利,还在最后做了归一化。(即,余弦相似度的分母部分) ```python class MyNet(paddle.nn.Layer): def __init__(self): super(MyNet, self).__init__() - self.conv1 = paddle.nn.Conv2D(in_channels=3, - out_channels=32, + self.conv1 = paddle.nn.Conv2D(in_channels=3, + out_channels=32, kernel_size=(3, 3), stride=2) - - self.conv2 = paddle.nn.Conv2D(in_channels=32, - out_channels=64, - kernel_size=(3,3), - stride=2) - - self.conv3 = paddle.nn.Conv2D(in_channels=64, - out_channels=128, + + self.conv2 = paddle.nn.Conv2D(in_channels=32, + out_channels=64, kernel_size=(3,3), stride=2) - + + self.conv3 = paddle.nn.Conv2D(in_channels=64, + out_channels=128, + kernel_size=(3,3), + stride=2) + self.gloabl_pool = paddle.nn.AdaptiveAvgPool2D((1,1)) self.fc1 = paddle.nn.Linear(in_features=128, out_features=8) - + def forward(self, x): x = self.conv1(x) x = F.relu(x) @@ -258,7 +258,7 @@ class MyNet(paddle.nn.Layer): ``` # 5、模型训练: -将epoch设置为10,进行模型训练 +将 epoch 设置为 10,进行模型训练 ```python def train(model): @@ -268,27 +268,27 @@ def train(model): inverse_temperature = paddle.to_tensor(np.array([1.0/0.2], dtype='float32')) epoch_num = 10 - + opt = paddle.optimizer.Adam(learning_rate=0.0001, parameters=model.parameters()) - + for epoch in range(epoch_num): for batch_id, data in enumerate(pairs_train_reader()): anchors_data, positives_data = data[0], data[1] anchors = paddle.to_tensor(anchors_data) positives = paddle.to_tensor(positives_data) - + anchor_embeddings = model(anchors) positive_embeddings = model(positives) - - similarities = paddle.matmul(anchor_embeddings, positive_embeddings, transpose_y=True) + + similarities = paddle.matmul(anchor_embeddings, positive_embeddings, transpose_y=True) similarities = paddle.multiply(similarities, inverse_temperature) - + sparse_labels = paddle.arange(0, num_classes, dtype='int64') loss = F.cross_entropy(similarities, sparse_labels) - + if batch_id % 500 == 0: print("epoch: {}, batch_id: {}, loss is: {}".format(epoch, batch_id, loss.numpy())) loss.backward() @@ -300,7 +300,7 @@ train(model) ``` ```python -start training ... +start training ... epoch: 0, batch_id: 0, loss is: [2.2915785] epoch: 0, batch_id: 500, loss is: [2.4421422] epoch: 0, batch_id: 1000, loss is: [1.7860969] @@ -354,7 +354,7 @@ near_neighbours_per_example = 10 x_test_t = paddle.to_tensor(x_test) test_images_embeddings = model(x_test_t) -similarities_matrix = paddle.matmul(test_images_embeddings, test_images_embeddings, transpose_y=True) +similarities_matrix = paddle.matmul(test_images_embeddings, test_images_embeddings, transpose_y=True) indicies = paddle.argsort(similarities_matrix, descending=True) indicies = indicies.numpy() @@ -373,7 +373,7 @@ examples = np.empty( for row_idx in range(num_classes): examples_for_class = class_idx_to_test_idxs[row_idx] anchor_idx = random.choice(examples_for_class) - + examples[row_idx, 0] = x_test[anchor_idx] anchor_near_neighbours = indicies[anchor_idx][1:near_neighbours_per_example+1] for col_idx, nn_idx in enumerate(anchor_near_neighbours): @@ -382,7 +382,7 @@ for row_idx in range(num_classes): show_collage(examples) ``` # 7、使用 @to_static 进行动静转换: -动静转换(@to_static)通过解析 Python 代码(抽象语法树,下简称:AST) 实现一行代码即可将动态图转为静态图的功能,只需在待转化的函数前添加一个装饰器 @paddle.jit.to_static +动静转换(@to_static)通过解析 Python 代码(抽象语法树,下简称:AST) 实现一行代码即可将动态图转为静态图的功能,只需在待转化的函数前添加一个装饰器 @paddle.jit.to_static 使用 @to_static 即支持 可训练可部署 ,也支持只部署(详见模型导出) ,常见使用方式如下: @@ -433,7 +433,7 @@ out = net(x, y) # 动转静训练 paddle.jit.save(net, './net') # 导出预测模型 ``` -方式一和方式二的主要区别是,前者直接在 forward() 函数定义处装饰,后者显式调用了 jit.to_static()方法,默认会对 net.forward进行动静转换。 +方式一和方式二的主要区别是,前者直接在 forward() 函数定义处装饰,后者显式调用了 jit.to_static()方法,默认会对 net.forward 进行动静转换。 本实验使用 paddle.jit.to_static 实现动转静: 飞桨推荐使用 @paddle.jit.to_static 实现动转静,也被称为基于源代码转写的动态图转静态图,其基本原理是通过分析 Python 代码来将动态图代码转写为静态图代码,并在底层自动使用执行器运行,使用起来非常方便,只需要在原网络结构的 forward 前添加一个装饰器 paddle.jit.to_static 即可。 @@ -444,26 +444,26 @@ class MyNet2(paddle.nn.Layer): def __init__(self): super(MyNet2, self).__init__() - self.conv1 = paddle.nn.Conv2D(in_channels=3, - out_channels=32, + self.conv1 = paddle.nn.Conv2D(in_channels=3, + out_channels=32, kernel_size=(3, 3), stride=2) - - self.conv2 = paddle.nn.Conv2D(in_channels=32, - out_channels=64, - kernel_size=(3,3), - stride=2) - - self.conv3 = paddle.nn.Conv2D(in_channels=64, - out_channels=128, + + self.conv2 = paddle.nn.Conv2D(in_channels=32, + out_channels=64, kernel_size=(3,3), stride=2) - + + self.conv3 = paddle.nn.Conv2D(in_channels=64, + out_channels=128, + kernel_size=(3,3), + stride=2) + self.gloabl_pool = paddle.nn.AdaptiveAvgPool2D((1,1)) self.fc1 = paddle.nn.Linear(in_features=128, out_features=8) - - # 在forward 前添加 paddle.jit.to_static 装饰器 + + # 在 forward 前添加 paddle.jit.to_static 装饰器 @paddle.jit.to_static() def forward(self, x): x = self.conv1(x) @@ -487,13 +487,13 @@ print(model_info) ```python ------------------------------------------------------------------------------- - Layer (type) Input Shape Output Shape Param # + Layer (type) Input Shape Output Shape Param # =============================================================================== - Conv2D-4 [[10, 3, 32, 32]] [10, 32, 15, 15] 896 - Conv2D-5 [[10, 32, 15, 15]] [10, 64, 7, 7] 18,496 - Conv2D-6 [[10, 64, 7, 7]] [10, 128, 3, 3] 73,856 -AdaptiveAvgPool2D-2 [[10, 128, 3, 3]] [10, 128, 1, 1] 0 - Linear-2 [[10, 128]] [10, 8] 1,032 + Conv2D-4 [[10, 3, 32, 32]] [10, 32, 15, 15] 896 + Conv2D-5 [[10, 32, 15, 15]] [10, 64, 7, 7] 18,496 + Conv2D-6 [[10, 64, 7, 7]] [10, 128, 3, 3] 73,856 +AdaptiveAvgPool2D-2 [[10, 128, 3, 3]] [10, 128, 1, 1] 0 + Linear-2 [[10, 128]] [10, 8] 1,032 =============================================================================== Total params: 94,280 Trainable params: 94,280 @@ -517,7 +517,7 @@ train(model_2) ``` ```python -start training ... +start training ... epoch: 0, batch_id: 0, loss is: [2.2707999] epoch: 0, batch_id: 500, loss is: [2.2578232] epoch: 0, batch_id: 1000, loss is: [2.0026908] @@ -566,15 +566,15 @@ epoch: 9, batch_id: 1500, loss is: [1.7263882] 代码层面:将模型中所有的 layers 接口在静态图模式下执行以转为 Op ,从而生成完整的静态 Program -Tensor层面:将所有的 Parameters 和 Buffers 转为可导出的 Variable 参数( persistable=True ) +Tensor 层面:将所有的 Parameters 和 Buffers 转为可导出的 Variable 参数( persistable=True ) 通过 forward 导出预测模型 通过 forward 导出预测模型导出一般包括三个步骤: 切换 eval() 模式:类似 Dropout 、LayerNorm 等接口在 train() 和 eval() 的行为存在较大的差异,在模型导出前,请务必确认模型已切换到正确的模式,否则导出的模型在预测阶段可能出现输出结果不符合预期的情况。 -构造 InputSpec 信息:InputSpec 用于表示输入的shape、dtype、name信息,且支持用 None 表示动态shape(如输入的 batch_size 维度),是辅助动静转换的必要描述信息。 +构造 InputSpec 信息:InputSpec 用于表示输入的 shape、dtype、name 信息,且支持用 None 表示动态 shape(如输入的 batch_size 维度),是辅助动静转换的必要描述信息。 -调用 save 接口:调用 paddle.jit.save接口,若传入的参数是类实例,则默认对 forward 函数进行 @to_static 装饰,并导出其对应的模型文件和参数文件。 +调用 save 接口:调用 paddle.jit.save 接口,若传入的参数是类实例,则默认对 forward 函数进行 @to_static 装饰,并导出其对应的模型文件和参数文件。 如下是一个简单的示例: @@ -749,7 +749,7 @@ class SimpleNet(Layer): ``` 其中 input_spec 参数是长度为 2 的 list ,对应 forward 函数的 x 和 bias_info 两个参数。 input_spec 的最后一个元素是包含键名为 x 的 InputSpec 对象的 dict ,对应参数 bias_info 的 Tensor 签名信息。 -方式四:指定非Tensor参数类型 +方式四:指定非 Tensor 参数类型 目前,to_static 装饰器中的 input_spec 参数仅接收 InputSpec 类型对象。若被装饰函数的参数列表除了 Tensor 类型,还包含其他如 Int、 String 等非 Tensor 类型时,推荐在函数中使用 kwargs 形式定义非 Tensor 参数,如下述样例中的 use_act 参数。 @@ -797,7 +797,7 @@ near_neighbours_per_example = 10 x_test_t = paddle.to_tensor(x_test) test_images_embeddings = model_2(x_test_t) -similarities_matrix = paddle.matmul(test_images_embeddings, test_images_embeddings, transpose_y=True) +similarities_matrix = paddle.matmul(test_images_embeddings, test_images_embeddings, transpose_y=True) indicies = paddle.argsort(similarities_matrix, descending=True) indicies = indicies.numpy() @@ -816,7 +816,7 @@ examples = np.empty( for row_idx in range(num_classes): examples_for_class = class_idx_to_test_idxs[row_idx] anchor_idx = random.choice(examples_for_class) - + examples[row_idx, 0] = x_test[anchor_idx] anchor_near_neighbours = indicies[anchor_idx][1:near_neighbours_per_example+1] for col_idx, nn_idx in enumerate(anchor_near_neighbours): @@ -829,31 +829,30 @@ show_collage(examples) 上述动态图转静态图的过程中,总体来说感觉还是可以的,具体总结了以下几个层面: - 1、接口层面: - 接口功能目前使用中覆盖了所有使用场景;InputSpec 指定信息也好用 + 接口功能目前使用中覆盖了所有使用场景;InputSpec 指定信息也好用 - 2、语法层面: - 语法支持方面尚未发现问题,我认为语法支持比较完备,但是图片展示功能(show_collage(examples))在运行时展示不出来;控制流语法转换比较流畅 + 语法支持方面尚未发现问题,我认为语法支持比较完备,但是图片展示功能(show_collage(examples))在运行时展示不出来;控制流语法转换比较流畅 - 3、报错层面: - 报错信息可读性差比较好,比如: - Traceback (most recent call last): - File "D:\Postgraduate\deep_learning\jiaoliu\pp\train.py", line 191, in - indicies = paddle.argsort(similarities_matrix, descending=True) - File "F:\Users\ASUS\anaconda3\envs\paddlepaddle-gpu\lib\site-packages\paddle\tensor\search.py", line 92, in argsort - _, ids = _C_ops.argsort(x, 'axis', axis, 'descending', descending) - SystemError: (Fatal) Operator argsort raises an struct paddle::memory::allocation::BadAlloc exception. - The exception content is - :ResourceExhaustedError: - - Out of memory error on GPU 0. Cannot allocate 762.939697MB memory on GPU 0, 3.256321GB memory has been allocated and available memory is only 761.527736MB. - - Please check whether there is any other process using GPU 0. - 1. If yes, please stop them, or start PaddlePaddle on another GPU. - 2. If no, please decrease the batch size of your model. - 但如果没有详细的提示信息还需上网查找解决方案;调试工具比较易用 + 报错信息可读性差比较好,比如: + Traceback (most recent call last): + File "D:\Postgraduate\deep_learning\jiaoliu\pp\train.py", line 191, in + indicies = paddle.argsort(similarities_matrix, descending=True) + File "F:\Users\ASUS\anaconda3\envs\paddlepaddle-gpu\lib\site-packages\paddle\tensor\search.py", line 92, in argsort + _, ids = _C_ops.argsort(x, 'axis', axis, 'descending', descending) + SystemError: (Fatal) Operator argsort raises an struct paddle::memory::allocation::BadAlloc exception. + The exception content is + :ResourceExhaustedError: + + Out of memory error on GPU 0. Cannot allocate 762.939697MB memory on GPU 0, 3.256321GB memory has been allocated and available memory is only 761.527736MB. + + Please check whether there is any other process using GPU 0. + 1. If yes, please stop them, or start PaddlePaddle on another GPU. + 2. If no, please decrease the batch size of your model. + 但如果没有详细的提示信息还需上网查找解决方案;调试工具比较易用 - 4、文档层面: - 从官方文档来说,动态图转静态图的示例文档感觉不太完善,例如paddle.jit.load等的API没有在使用指南的示例文档中展现,教程文档还有待完善,其他方面感觉内容比较详细丰富,具有较好的指导性 + 从官方文档来说,动态图转静态图的示例文档感觉不太完善,例如 paddle.jit.load 等的 API 没有在使用指南的示例文档中展现,教程文档还有待完善,其他方面感觉内容比较详细丰富,具有较好的指导性 - 5、意见建议: - 动态图转静态图的代码还是比较方便的,但对新手来说有一定难度,建议在使用指南中可以适当增加一些重点难点视频解说; - 有一些官网上的使用指南用于学习,但是有一些是在其使用指南中没有介绍的,例如数据集的拆分等这些需要自己去琢磨,可再丰富一下指南内容; - 使用指南中的方式或方法比较多的可以用序号组合一下,方便学习者更快的了解每种方式或方法的优缺点; - paddle自带数据集太大,数据处理需消耗大量资源,使用起来不太方便,建议新增一些小的数据集。 - + 动态图转静态图的代码还是比较方便的,但对新手来说有一定难度,建议在使用指南中可以适当增加一些重点难点视频解说; + 有一些官网上的使用指南用于学习,但是有一些是在其使用指南中没有介绍的,例如数据集的拆分等这些需要自己去琢磨,可再丰富一下指南内容; + 使用指南中的方式或方法比较多的可以用序号组合一下,方便学习者更快的了解每种方式或方法的优缺点; + paddle 自带数据集太大,数据处理需消耗大量资源,使用起来不太方便,建议新增一些小的数据集。 diff --git "a/docs/eval/\343\200\220Hackathon No.69\343\200\221PR.md" "b/docs/eval/\343\200\220Hackathon No.69\343\200\221PR.md" index 9758472efbb..871d7cac31b 100644 --- "a/docs/eval/\343\200\220Hackathon No.69\343\200\221PR.md" +++ "b/docs/eval/\343\200\220Hackathon No.69\343\200\221PR.md" @@ -11,27 +11,27 @@ # 一、摘要 -相关背景:飞桨框架于 2.0 正式版全面支持了动态图训练,并在2.1、2.2 两个大版本中不断新增了API以及大幅增强了训练功能。希望有人对于飞桨框架动态图下单机训练功能整体的使用感受,可以与其他深度学习框架做功能对比,包括API、Tensor 索引、NumPy Compatibility、报错信息提示、训练性能、以及各种 trick 的用法等,并产出一份对应的评估报告。 +相关背景:飞桨框架于 2.0 正式版全面支持了动态图训练,并在 2.1、2.2 两个大版本中不断新增了 API 以及大幅增强了训练功能。希望有人对于飞桨框架动态图下单机训练功能整体的使用感受,可以与其他深度学习框架做功能对比,包括 API、Tensor 索引、NumPy Compatibility、报错信息提示、训练性能、以及各种 trick 的用法等,并产出一份对应的评估报告。 -本评估方案将从以下几个方面对paddle动态图单机训练功能进行体验评估: +本评估方案将从以下几个方面对 paddle 动态图单机训练功能进行体验评估: 1、环境配置及开启动态图模式 -2、API使用及对比 +2、API 使用及对比 -调用高层API:如:paddle.Model、paddle.vision,与pytorch框架做对比。并在LeNet、ResNet等网络模型或模型自己组网(Sequential组网、SubClass组网)训练中进行评估。 +调用高层 API:如:paddle.Model、paddle.vision,与 pytorch 框架做对比。并在 LeNet、ResNet 等网络模型或模型自己组网(Sequential 组网、SubClass 组网)训练中进行评估。 3、Tensor 索引 -在模型训练中体验了Tensor在数据传递过程中的表现(如:了解索引和 其切片规则、访问与修改Tensor、逻辑相关函数重写规则),并体验了使用指南里有关Tensor的所有基本操作。 +在模型训练中体验了 Tensor 在数据传递过程中的表现(如:了解索引和 其切片规则、访问与修改 Tensor、逻辑相关函数重写规则),并体验了使用指南里有关 Tensor 的所有基本操作。 -4、NumPy兼容性分析及对比 +4、NumPy 兼容性分析及对比 -在动态图模型代码中,所有与组网相关的 numpy 操作都必须用 paddle 的 API 重新实现,所以在模型训练过程中体验Paddle.API来感受对比Pytorch的表现;分析了Tensor兼容Numpy数组的同时,优先使用Tensor的两种场景。 +在动态图模型代码中,所有与组网相关的 numpy 操作都必须用 paddle 的 API 重新实现,所以在模型训练过程中体验 Paddle.API 来感受对比 Pytorch 的表现;分析了 Tensor 兼容 Numpy 数组的同时,优先使用 Tensor 的两种场景。 5、动态图单机训练 -体验控制流和共享权重的使用效果,然后在数据集定义、加载和数据预处理、数据增强方面感受与Pytorch使用的区别,最后通过LeNet举例说明训练结果,并进行了对比分析 +体验控制流和共享权重的使用效果,然后在数据集定义、加载和数据预处理、数据增强方面感受与 Pytorch 使用的区别,最后通过 LeNet 举例说明训练结果,并进行了对比分析 6、各种 trick 的用法体验 @@ -46,40 +46,40 @@ | CPU | Intel(R)Core(TM)i5-7200U CPU @2.50GHz | | 内存 | 12GB DDR4 | | GPU | NVIDIA GeForce 940MX | -| 系统平台 | Window 10 家庭中文版(64位) | +| 系统平台 | Window 10 家庭中文版(64 位) | | 软件环境 | Paddle2.2、 Pytorch3.8、Cuda 10.1、Anaconda3 | -Paddle环境安装参考: https://www.paddlepaddle.org.cn/install/quick?docurl=/documentation/docs/zh/install/pip/windows-pip.html +Paddle 环境安装参考: https://www.paddlepaddle.org.cn/install/quick?docurl=/documentation/docs/zh/install/pip/windows-pip.html -安装CPU版本时候使用到了清华镜像源:pip install -i https://pypi.tuna.tsinghua.edu.cn/simple paddlepaddle +安装 CPU 版本时候使用到了清华镜像源:pip install -i https://pypi.tuna.tsinghua.edu.cn/simple paddlepaddle -GPU版本:python -m pip install paddlepaddle-gpu==2.2.2.post101 -f https://www.paddlepaddle.org.cn/whl/windows/mkl/avx/stable.html +GPU 版本:python -m pip install paddlepaddle-gpu==2.2.2.post101 -f https://www.paddlepaddle.org.cn/whl/windows/mkl/avx/stable.html -Paddle与Pytorch环境配置使用对比: 在单机安装中,都安装在了conda环境,Paddle安装比较顺利,直接按照文档安装即可,与Pytorch安装没有太大区别,单机测试稳定性也都比较良好。 +Paddle 与 Pytorch 环境配置使用对比: 在单机安装中,都安装在了 conda 环境,Paddle 安装比较顺利,直接按照文档安装即可,与 Pytorch 安装没有太大区别,单机测试稳定性也都比较良好。 -# 三、API使用及对比 +# 三、API 使用及对比 -## 1、从PaddlePaddle的各个API目录来对比分析 +## 1、从 PaddlePaddle 的各个 API 目录来对比分析 -首先,基础操作类、组网类、Loss类、工具类、视觉类这五大类从映射Pytorch上看,在单机训练中可以满足训练及预测所需的API使用类别。 +首先,基础操作类、组网类、Loss 类、工具类、视觉类这五大类从映射 Pytorch 上看,在单机训练中可以满足训练及预测所需的 API 使用类别。 -其次,在单机模型训练中对比了一下主要API的使用 +其次,在单机模型训练中对比了一下主要 API 的使用 | 名称 | PaddlePaddle | Pytorch | | ------- | ----------------------------- | ------------------------------ | | layer | nn.Layer | nn.Module | -| 各种层 | nn.layer2D(即paddle使用大写D) | nn.layer2d(即Pytorch使用小写d) | +| 各种层 | nn.layer2D(即 paddle 使用大写 D) | nn.layer2d(即 Pytorch 使用小写 d) | | flatten | nn.Flatten | var.view(var.size(0), -1) | | concat | paddle.concat | torch.cat | | optim | paddle.optimizer | torch.optim | -## 2、从具体API的参数设计差异作对比分析 +## 2、从具体 API 的参数设计差异作对比分析 -通过训练和测试Paddle的动态图单机模型,就个人体验而言,对模型中常用到的一些API作简要分析,如paddle.to_tensor,paddle.save,paddle.load,paddle.nn.Conv2D , paddle.nn.Linear, paddle.nn.CrossEntropyLoss , paddle.io.DataLoader +通过训练和测试 Paddle 的动态图单机模型,就个人体验而言,对模型中常用到的一些 API 作简要分析,如 paddle.to_tensor,paddle.save,paddle.load,paddle.nn.Conv2D , paddle.nn.Linear, paddle.nn.CrossEntropyLoss , paddle.io.DataLoader -### 2.1 基础操作类API +### 2.1 基础操作类 API ```python #paddle.to_tensor @@ -95,9 +95,9 @@ torch.tensor(data, pin_memory=False) ``` -在paddle.to_tensor中,stop_gradient表示是否阻断梯度传导,PyTorch的requires_grad表示是否不阻断梯度传导。 +在 paddle.to_tensor 中,stop_gradient 表示是否阻断梯度传导,PyTorch 的 requires_grad 表示是否不阻断梯度传导。 -在torch.tensor中,pin_memeory表示是否使用锁页内存,而PaddlePaddle却无此参数。 +在 torch.tensor 中,pin_memeory 表示是否使用锁页内存,而 PaddlePaddle 却无此参数。 ------ @@ -112,10 +112,10 @@ torch.load(f, **pickle_load_args) ``` -在torch.load中, pickle_module 表示用于unpickling元数据和对象的模块,PaddlePaddle无此参数。 map_location 表示加载模型的位置,PaddlePaddle无此参数。 +在 torch.load 中, pickle_module 表示用于 unpickling 元数据和对象的模块,PaddlePaddle 无此参数。 map_location 表示加载模型的位置,PaddlePaddle 无此参数。 -在加载内容上,PyTorch可以加载torch.Tensor、torch.nn.Module、优化器等多个类型的数据。 -PaddlePaddle只能加载paddle.nn.Layer、优化器这两个类型的数据,这方面Pytorch更优一些。 +在加载内容上,PyTorch 可以加载 torch.Tensor、torch.nn.Module、优化器等多个类型的数据。 +PaddlePaddle 只能加载 paddle.nn.Layer、优化器这两个类型的数据,这方面 Pytorch 更优一些。 ------ @@ -130,15 +130,15 @@ torch.save(obj, pickle_protocol=2) ``` -在paddle.save中, path表示存储的路径,这一点比 PyTorch 的f更为清晰一些。 +在 paddle.save 中, path 表示存储的路径,这一点比 PyTorch 的 f 更为清晰一些。 -在torch.save中, pickle_module 表示用于pickling元数据和对象的模块,PaddlePaddle无此参数。 +在 torch.save 中, pickle_module 表示用于 pickling 元数据和对象的模块,PaddlePaddle 无此参数。 -还有在存储内容上,跟paddle.load情况类似,PaddlePaddle只能存储paddle.nn.Layer、优化器这两个类型的数据,个人觉得这方面PaddlePaddle有待加强。 +还有在存储内容上,跟 paddle.load 情况类似,PaddlePaddle 只能存储 paddle.nn.Layer、优化器这两个类型的数据,个人觉得这方面 PaddlePaddle 有待加强。 ------ -### 2.2 组网类API +### 2.2 组网类 API ```python #paddle.nn.Conv2D @@ -165,7 +165,7 @@ torch.nn.Conv2d(in_channels, padding_mode='zeros') ``` -在paddle.nn.Conv2D中,PaddlePaddle支持NCHW和NHWC两种格式的输入(通过data_format设置)。而PyTorch只支持NCHW的输入,这一点PaddlePaddle更优一些。 +在 paddle.nn.Conv2D 中,PaddlePaddle 支持 NCHW 和 NHWC 两种格式的输入(通过 data_format 设置)。而 PyTorch 只支持 NCHW 的输入,这一点 PaddlePaddle 更优一些。 ------ @@ -176,11 +176,11 @@ paddle.nn.Linear(in_features, out_features, weight_attr=None, bias_attr=None, na torch.nn.Linear(in_features, out_features, bias=True) ``` -在paddle.nn.Linear中,weight_attr/bias_attr默认使用默认的权重/偏置参数属性,否则为指定的权重/偏置参数属性,而PyTorch的bias默认为True,表示使用可更新的偏置参数。需要注意的是在PaddlePaddle中,当bias_attr设置为bool类型与PyTorch的作用一致。 +在 paddle.nn.Linear 中,weight_attr/bias_attr 默认使用默认的权重/偏置参数属性,否则为指定的权重/偏置参数属性,而 PyTorch 的 bias 默认为 True,表示使用可更新的偏置参数。需要注意的是在 PaddlePaddle 中,当 bias_attr 设置为 bool 类型与 PyTorch 的作用一致。 ------ -### 2.3 Loss类API +### 2.3 Loss 类 API ```python #paddle.nn.CrossEntropyLoss @@ -199,11 +199,11 @@ torch.nn.CrossEntropyLoss(weight=None, reduction='mean') ``` -在paddle.nn.CrossEntropyLoss中, use_softmax 表示在使用交叉熵之前是否计算softmax,PyTorch无此参数;soft_label指明label是否为软标签,PyTorch无此参数;而axis表示进行softmax计算的维度索引,PyTorch无此参数。 在这个API中,个人感觉PaddlePaddle的表现优于PyTorch。 +在 paddle.nn.CrossEntropyLoss 中, use_softmax 表示在使用交叉熵之前是否计算 softmax,PyTorch 无此参数;soft_label 指明 label 是否为软标签,PyTorch 无此参数;而 axis 表示进行 softmax 计算的维度索引,PyTorch 无此参数。 在这个 API 中,个人感觉 PaddlePaddle 的表现优于 PyTorch。 ------ -### 2.4 工具类API +### 2.4 工具类 API ```python #paddle.io.DataLoader @@ -239,25 +239,25 @@ torch.utils.data.DataLoader(dataset, persistent_workers=False) ``` -在paddle.io.DataLoader中, feed_list 表示feed变量列表,PyTorch无此参数。 use_shared_memory 表示是否使用共享内存来提升子进程将数据放入进程间队列的速度,PyTorch无此参数。 +在 paddle.io.DataLoader 中, feed_list 表示 feed 变量列表,PyTorch 无此参数。 use_shared_memory 表示是否使用共享内存来提升子进程将数据放入进程间队列的速度,PyTorch 无此参数。 -在torch.utils.data.DataLoader中,prefetch_factor 表示每个worker预先加载的数据数量,PaddlePaddle无此参数;还有就是PyTorch可通过设置sampler自定义数据采集器,PaddlePaddle只能自定义一个DataLoader来实现该功能,会有些繁琐。总的来说,这部分Pytorch的体验更好一些。 +在 torch.utils.data.DataLoader 中,prefetch_factor 表示每个 worker 预先加载的数据数量,PaddlePaddle 无此参数;还有就是 PyTorch 可通过设置 sampler 自定义数据采集器,PaddlePaddle 只能自定义一个 DataLoader 来实现该功能,会有些繁琐。总的来说,这部分 Pytorch 的体验更好一些。 ------ -​ 从整体的API使用上,感觉paddle升级后的 paddle.xxx (例如:paddle.device paddle.nn paddle.vision )比之前的 padddle.fluid.xxx 好用很多,还有就是新增加的高层API个人比较喜欢,一是对初学者比较友好、易用,二是对于开发者可以节省代码量,更简洁直观一些,在(六、动态图单机训练)中进行了代码展示和对比分析。 +​ 从整体的 API 使用上,感觉 paddle 升级后的 paddle.xxx (例如:paddle.device paddle.nn paddle.vision )比之前的 padddle.fluid.xxx 好用很多,还有就是新增加的高层 API 个人比较喜欢,一是对初学者比较友好、易用,二是对于开发者可以节省代码量,更简洁直观一些,在(六、动态图单机训练)中进行了代码展示和对比分析。 -与Pytorch相比,基础API的结构和调用没有太大区别,但是在速度上,paddle的基础API会更快一点,如果是利用了paddle高层API,速度会快很多,在同样5次epoch的情况下,LeNet训练高层API用38s左右,基础API得用将近两分钟,所以用高层API能减少大约三分之二的训练时间。 +与 Pytorch 相比,基础 API 的结构和调用没有太大区别,但是在速度上,paddle 的基础 API 会更快一点,如果是利用了 paddle 高层 API,速度会快很多,在同样 5 次 epoch 的情况下,LeNet 训练高层 API 用 38s 左右,基础 API 得用将近两分钟,所以用高层 API 能减少大约三分之二的训练时间。 -总体来说,使用像paddle.Model、paddle.vision这样的高级API进行封装调用,使用体验比较好,个人感觉在以后深度学习模型普遍使用时,高层API会更受欢迎,也会成为模型训练测试中更为流行的一种方法。 +总体来说,使用像 paddle.Model、paddle.vision 这样的高级 API 进行封装调用,使用体验比较好,个人感觉在以后深度学习模型普遍使用时,高层 API 会更受欢迎,也会成为模型训练测试中更为流行的一种方法。 # 四、Tensor 索引 -在了解Paddle的Tensor索引和其切片规则以及逻辑相关函数重写规则等内容后,结合指南内容( https://www.paddlepaddle.org.cn/documentation/docs/zh/guides/01_paddle2.0_introduction/basic_concept/tensor_introduction_cn.html#id1 )和模型训练过程中的Tensor索引使用,共有以下几点体验总结: +在了解 Paddle 的 Tensor 索引和其切片规则以及逻辑相关函数重写规则等内容后,结合指南内容( https://www.paddlepaddle.org.cn/documentation/docs/zh/guides/01_paddle2.0_introduction/basic_concept/tensor_introduction_cn.html#id1 )和模型训练过程中的 Tensor 索引使用,共有以下几点体验总结: -**一、Paddle可以使用静态数组索引;不可以使用tensor索引。** +**一、Paddle 可以使用静态数组索引;不可以使用 tensor 索引。** 示例:广播 (broadcasting) 1.每个张量至少为一维张量 @@ -271,42 +271,42 @@ y = paddle.ones((2, 3, 4)) z = x + y print(z.shape) # [2, 3, 4] - + x = paddle.ones((2, 3, 1, 5)) y = paddle.ones((3, 4, 1)) # 从后向前依次比较: -# 第一次:y的维度大小是1 -# 第二次:x的维度大小是1 -# 第三次:x和y的维度大小相等 -# 第四次:y的维度不存在 -# 所以 x和y是可以广播的 +# 第一次:y 的维度大小是 1 +# 第二次:x 的维度大小是 1 +# 第三次:x 和 y 的维度大小相等 +# 第四次:y 的维度不存在 +# 所以 x 和 y 是可以广播的 z = x + y print(z.shape) # [2, 3, 4, 5] - + # 相反 x = paddle.ones((2, 3, 4)) y = paddle.ones((2, 3, 6)) -# 此时x和y是不可广播的,因为第一次比较 4不等于6 +# 此时 x 和 y 是不可广播的,因为第一次比较 4 不等于 6 # z = x + y # InvalidArgumentError: Broadcast dimension mismatch. ``` **二、两个张量进行广播语义后的结果张量的形状计算规则如下:** -1.如果两个张量的形状的长度不一致,那么需要在较小形状长度的矩阵向前添加1,直到两个张量的形状长度相等。 +1.如果两个张量的形状的长度不一致,那么需要在较小形状长度的矩阵向前添加 1,直到两个张量的形状长度相等。 2.保证两个张量形状相等之后,每个维度上的结果维度就是当前维度上较大的那个。 ```python import paddle - + x = paddle.ones((2, 1, 4)) y = paddle.ones((3, 1)) z = x + y print(z.shape) -# z的形状: [2,3,4] - +# z 的形状: [2,3,4] + x = paddle.ones((2, 1, 4)) y = paddle.ones((3, 2)) @@ -314,26 +314,26 @@ y = paddle.ones((3, 2)) # ValueError: (InvalidArgument) Broadcast dimension mismatch. ``` -**三、Paddle 目前支持的Tensor索引规则:** +**三、Paddle 目前支持的 Tensor 索引规则:** -**Paddle 目前支持的Tensor索引状态:** +**Paddle 目前支持的 Tensor 索引状态:** 1、基于 0-n 的下标进⾏索引 2、如果下标为负数,则从尾部开始 3、通过冒号 : 分隔切⽚参数 start:stop:step 来进⾏切⽚操作,其中 start、stop、step 均可缺省 -示例1:索引 +示例 1:索引 ```python ndim_1_tensor = paddle.to_tensor([0, 1, 2, 3, 4, 5, 6, 7, 8]) -print("最初的Tensor: ", ndim_1_tensor.numpy()) +print("最初的 Tensor: ", ndim_1_tensor.numpy()) print("取⾸端元素:", ndim_1_tensor[0].numpy()) print("取末端元素:", ndim_1_tensor[-1].numpy()) print("取所有元素:", ndim_1_tensor[:].numpy()) -print("取索引3之前的所有元素:", ndim_1_tensor[:3].numpy()) -print("取从索引6开始的所有元素:", ndim_1_tensor[6:].numpy()) -print("取从索引3开始到索引6之前的所有元素:", ndim_1_tensor[3:6].numpy()) -print("间隔3取所有元素:", ndim_1_tensor[::3].numpy()) +print("取索引 3 之前的所有元素:", ndim_1_tensor[:3].numpy()) +print("取从索引 6 开始的所有元素:", ndim_1_tensor[6:].numpy()) +print("取从索引 3 开始到索引 6 之前的所有元素:", ndim_1_tensor[3:6].numpy()) +print("间隔 3 取所有元素:", ndim_1_tensor[::3].numpy()) print("逆序取所有元素:", ndim_1_tensor[::-1].numpy()) ``` @@ -350,20 +350,20 @@ Interval of 3: [0 3 6] Reverse: [8 7 6 5 4 3 2 1 0] ``` -**Paddle 目前不支持的Tensor索引状态:** +**Paddle 目前不支持的 Tensor 索引状态:** -示例1:不能维度直接赋值 +示例 1:不能维度直接赋值 ```python #报错: TypeError: 'paddle.fluid.libpaddle.VarBase' object does not support item assignment -#代码如下: +#代码如下: # pytorch code Pred_boxes[:, 0] = pred_ctr_x - 0.5 * pred_w pred_boxes[:, 1] = pred_ctr_y - 0.5 * pred_h pred_boxes[:, 2] = pred_ctr_x + 0.5 * pred_w pred_boxes[:, 3] = pred_ctr_y + 0.5 * pred_h - + # paddlepaddle code pred_boxes = paddle.layers.concat([ pred_ctr_x - 0.5 * pred_w, @@ -376,23 +376,23 @@ pred_boxes = paddle.layers.concat([ #维度报错: too many indices (3) for tensor of dimension 2 -#代码如下: +#代码如下: # pytorch code -bbox_x[bind, :, np.newaxis ] +bbox_x[bind, :, np.newaxis ] # paddlepaddle code paddle.layers.reshape(bbox_x[bind, :], [1, -1, 1]) ``` -示例2: tensor的值不能直接利用 +示例 2: tensor 的值不能直接利用 -报错:paddlepaddle中的value不能直接拿出来用。 +报错:paddlepaddle 中的 value 不能直接拿出来用。 ```python TypeError: The type of 'shape' in reshape must be list[int] or tuple(int) in Dygraph mode, but received , which contains Variable. -#错误代码:其中stack_size, feat_size 为 tensor。 +#错误代码:其中 stack_size, feat_size 为 tensor。 -#代码如下: +#代码如下: # paddlepaddle code shift_x1 = paddle.layers.reshape(paddle.dygraph.to_variable(shift_x1), [1, stack_size, feat_size[1]]) @@ -408,21 +408,21 @@ feat_size = feat_size.numpy() **四、Tensor 索引整体体验** -感觉在通过索引或切片修改 Tensor 的整体过程有些冗余,稳定性也会下降。虽然使用指南里说明了修改会导致原值不会被保存,可能会给梯度计算引入风险 ,但是在这点上个人感觉Pytorch的体验要好于Paddle。 +感觉在通过索引或切片修改 Tensor 的整体过程有些冗余,稳定性也会下降。虽然使用指南里说明了修改会导致原值不会被保存,可能会给梯度计算引入风险 ,但是在这点上个人感觉 Pytorch 的体验要好于 Paddle。 -总的来说,在模型训练中利用Tensor加载数据集等操作上 Pytorch与 Paddle的体验并没有太大区别,但整体的感觉Pytorch的Tensor 索引更好一些,个人感觉Paddle在修改 Tensor的部分上可以增加一些文档说明。 +总的来说,在模型训练中利用 Tensor 加载数据集等操作上 Pytorch 与 Paddle 的体验并没有太大区别,但整体的感觉 Pytorch 的 Tensor 索引更好一些,个人感觉 Paddle 在修改 Tensor 的部分上可以增加一些文档说明。 **文档序号错误小提醒:** - https://www.paddlepaddle.org.cn/documentation/docs/zh/guides/01_paddle2.0_introduction/update_cn.html#tensor 中的“使用Tensor概念表示数据”下的序号应为1、2、;文档中为两个1、1、。 + https://www.paddlepaddle.org.cn/documentation/docs/zh/guides/01_paddle2.0_introduction/update_cn.html#tensor 中的“使用 Tensor 概念表示数据”下的序号应为 1、2、;文档中为两个 1、1、。 -# 五、NumPy兼容性分析及对比 +# 五、NumPy 兼容性分析及对比 -NumPy在Paddle的体验,感觉和Pytorch的体验并无区别,但是在阅读使用文档时的体验感较好,内容叙述很详细 (文档链接:https://www.paddlepaddle.org.cn/tutorials/projectdetail/3466356 ) +NumPy 在 Paddle 的体验,感觉和 Pytorch 的体验并无区别,但是在阅读使用文档时的体验感较好,内容叙述很详细 (文档链接:https://www.paddlepaddle.org.cn/tutorials/projectdetail/3466356 ) -**1、关于numpy API的重写** +**1、关于 numpy API 的重写** -在Paddle动态图单机训练中,所有与组网相关的 numpy 操作都必须用 paddle 的 API 重新实现 ,这一点个人认为需要注意,因为在习惯使用Pytorch代码逻辑时,转为PaddlePaddle容易出错,下面举例说明: +在 Paddle 动态图单机训练中,所有与组网相关的 numpy 操作都必须用 paddle 的 API 重新实现 ,这一点个人认为需要注意,因为在习惯使用 Pytorch 代码逻辑时,转为 PaddlePaddle 容易出错,下面举例说明: ```python #下述样例需要将 forward 中的所有的 numpy 操作都转为 Paddle API: @@ -438,11 +438,11 @@ def forward(self, x): return out ``` -注:由于在动态图模型代码中的 numpy 相关的操作不可以转为静态图,所以在动态图单机训练时候,只要与组网相关的 numpy 操作用 paddle 的 API 重新实现即可,所以在numpy API的重写部分,记住以上区别可以防止 Segment Fault 等错误的产生。 +注:由于在动态图模型代码中的 numpy 相关的操作不可以转为静态图,所以在动态图单机训练时候,只要与组网相关的 numpy 操作用 paddle 的 API 重新实现即可,所以在 numpy API 的重写部分,记住以上区别可以防止 Segment Fault 等错误的产生。 -**2、关于Tensor 操作的支持** +**2、关于 Tensor 操作的支持** -在动态图单机训练中,感觉Paddle的Tensor高度兼容Numpy数组(array),发现增加了很多适用于深度学习任务的参数和方法,如反向计算梯度,更灵活的指定运行硬件 ,还有就是Paddle的Tensor可以与Numpy的数组方便的互转 ,比如以下代码展示: +在动态图单机训练中,感觉 Paddle 的 Tensor 高度兼容 Numpy 数组(array),发现增加了很多适用于深度学习任务的参数和方法,如反向计算梯度,更灵活的指定运行硬件 ,还有就是 Paddle 的 Tensor 可以与 Numpy 的数组方便的互转 ,比如以下代码展示: ```python import paddle @@ -450,32 +450,32 @@ import numpy as np tensor_to_convert = paddle.to_tensor([1.,2.]) -#通过 Tensor.numpy() 方法,将 Tensor 转化为 Numpy数组 +#通过 Tensor.numpy() 方法,将 Tensor 转化为 Numpy 数组 tensor_to_convert.numpy() -#通过paddle.to_tensor() 方法,将 Numpy数组 转化为 Tensor +#通过 paddle.to_tensor() 方法,将 Numpy 数组 转化为 Tensor tensor_temp = paddle.to_tensor(np.array([1.0, 2.0])) ``` -**3、numpy与tensor的转换补充** +**3、numpy 与 tensor 的转换补充** -numpy操作多样, 简单. 但网络前向只能是tensor类型, 各有优势, 所以需要相互转换补充. +numpy 操作多样, 简单. 但网络前向只能是 tensor 类型, 各有优势, 所以需要相互转换补充. ```python -# convert Tensor x of torch to array y of numpy: +# convert Tensor x of torch to array y of numpy: y = x.numpy(); - -# convert array x of numpy to Tensor y of torch: + +# convert array x of numpy to Tensor y of torch: y = torch.from_numpy(x) - -# 先将数据转换成Tensor, 再使用CUDA函数来将Tensor移动到GPU上加速 -如果想把CUDA tensor格式的数据改成numpy时,需要先将其转换成cpu float-tensor随后再转到numpy格式。 + +# 先将数据转换成 Tensor, 再使用 CUDA 函数来将 Tensor 移动到 GPU 上加速 +如果想把 CUDA tensor 格式的数据改成 numpy 时,需要先将其转换成 cpu float-tensor 随后再转到 numpy 格式。 x_np = x.data.numpy() - + # 改为: - + x_np = x.data.cpu().numpy() - + # 或者兼容上面两者的方式 x_np = x.detach().cpu().numpy() if x.requires_grad else x.cpu().numpy() @@ -483,18 +483,18 @@ x_np = x.detach().cpu().numpy() if x.requires_grad else x.cpu().numpy() **整体体验:** -感觉对于刚使用Paddle的新手,这部分需要注意的就是 Paddle的Tensor虽然可以与Numpy的数组方便的互相转换 ,但是有两个场景优先使用Paddle的Tensor 比较好: +感觉对于刚使用 Paddle 的新手,这部分需要注意的就是 Paddle 的 Tensor 虽然可以与 Numpy 的数组方便的互相转换 ,但是有两个场景优先使用 Paddle 的 Tensor 比较好: -- 场景一:在组网程序中,对网络中向量的处理,务必使用Tensor,而不建议转成Numpy的数组。如果在组网过程中转成Numpy的数组,并使用Numpy的函数会拖慢整体性能; -- 场景二:在数据处理和模型后处理等场景,建议优先使用Tensor,主要是飞桨为AI硬件做了大量的适配和性能优化工作,部分情况下会获得更好的使用体验和性能。 +- 场景一:在组网程序中,对网络中向量的处理,务必使用 Tensor,而不建议转成 Numpy 的数组。如果在组网过程中转成 Numpy 的数组,并使用 Numpy 的函数会拖慢整体性能; +- 场景二:在数据处理和模型后处理等场景,建议优先使用 Tensor,主要是飞桨为 AI 硬件做了大量的适配和性能优化工作,部分情况下会获得更好的使用体验和性能。 建议:这两个场景内容可以增加一些实例,可能会使新手在这部分的理解更为透彻。 -总体来说:Tensor与Numpy数组的兼容与转换,Paddle体验更好一点,兼容性上与Pytorch感觉没区别,但是Paddle的兼容转换处理上更具有一些前瞻性。 +总体来说:Tensor 与 Numpy 数组的兼容与转换,Paddle 体验更好一点,兼容性上与 Pytorch 感觉没区别,但是 Paddle 的兼容转换处理上更具有一些前瞻性。 # 六、动态图单机训练 -(1)使用 Pytorch 完成一个图像分类的动态图单机训练例子(MNIST数据集) +(1)使用 Pytorch 完成一个图像分类的动态图单机训练例子(MNIST 数据集) ```python import torch @@ -581,7 +581,7 @@ for t in range(epoch): print("Done!") ``` -(2)使用 Paddle 完成一个图像分类的动态图单机训练例子(MNIST数据集) +(2)使用 Paddle 完成一个图像分类的动态图单机训练例子(MNIST 数据集) ```python import paddle @@ -589,7 +589,7 @@ from paddle.vision.transforms import Compose, Normalize transform = Compose([Normalize(mean=[127.5], std=[127.5], data_format='CHW')]) -# 使用transform对数据集做归一化 +# 使用 transform 对数据集做归一化 print('download training data and load training data') train_dataset = paddle.vision.datasets.MNIST(mode='train', transform=transform) test_dataset = paddle.vision.datasets.MNIST(mode='test', transform=transform) @@ -622,10 +622,10 @@ class LeNet(paddle.nn.Layer): x = F.relu(x) x = self.linear3(x) return x - -#方法一 高层API + +#方法一 高层 API from paddle.metric import Accuracy -model = paddle.Model(LeNet()) # 用Model封装模型 +model = paddle.Model(LeNet()) # 用 Model 封装模型 optim = paddle.optimizer.Adam(learning_rate=0.001, parameters=model.parameters()) # 配置模型 @@ -643,7 +643,7 @@ model.fit(train_dataset, ) model.evaluate(test_dataset, batch_size=64, verbose=1) -#方法2 基础API +#方法 2 基础 API import paddle.nn.functional as F train_loader = paddle.io.DataLoader(train_dataset, batch_size=64, shuffle=True) # 加载训练集 batch_size 设为 64 @@ -651,7 +651,7 @@ def train(model): model.train() epochs = 2 optim = paddle.optimizer.Adam(learning_rate=0.001, parameters=model.parameters()) - # 用Adam作为优化函数 + # 用 Adam 作为优化函数 for epoch in range(epochs): for batch_id, data in enumerate(train_loader()): x_data = data[0] @@ -687,33 +687,33 @@ test(model) (3)两个程序的运行结果 -一、Pytorch程序运行结果 +一、Pytorch 程序运行结果 ```python #下载数据 Downloading http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz Downloading http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz to ./data\MNIST\raw\train-images-idx3-ubyte.gz -9913344it [00:03, 2813467.92it/s] +9913344it [00:03, 2813467.92it/s] Extracting ./data\MNIST\raw\train-images-idx3-ubyte.gz to ./data\MNIST\raw Downloading http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz Downloading http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz to ./data\MNIST\raw\train-labels-idx1-ubyte.gz -29696it [00:00, 29740700.00it/s] +29696it [00:00, 29740700.00it/s] Extracting ./data\MNIST\raw\train-labels-idx1-ubyte.gz to ./data\MNIST\raw Downloading http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz Downloading http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz to ./data\MNIST\raw\t10k-images-idx3-ubyte.gz -1649664it [00:01, 1119159.12it/s] +1649664it [00:01, 1119159.12it/s] Extracting ./data\MNIST\raw\t10k-images-idx3-ubyte.gz to ./data\MNIST\raw Downloading http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz Downloading http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz to ./data\MNIST\raw\t10k-labels-idx1-ubyte.gz -5120it [00:00, 5302428.76it/s] +5120it [00:00, 5302428.76it/s] Extracting ./data\MNIST\raw\t10k-labels-idx1-ubyte.gz to ./data\MNIST\raw ``` ```python -#五次epoch结果 +#五次 epoch 结果 Epoch 1 ---------------------- train_loss:2.301828587023417 @@ -746,14 +746,14 @@ test_loss:0.3223567478398482 test_acc:0.9044666666666666 ``` -二、Paddle程序运行结果 +二、Paddle 程序运行结果 -由于paddle文档中提供的数据集下载代码一直报错(已在报错汇总中展示),故进行了手动下载数据集 +由于 paddle 文档中提供的数据集下载代码一直报错(已在报错汇总中展示),故进行了手动下载数据集 -​ 1、使用高层API结果 +​ 1、使用高层 API 结果 ```python -#附第5个epoch +#附第 5 个 epoch Epoch 5/5 step 10/938 [..............................] - loss: 0.0325 - acc: 0.9938 - ETA: 35s - 38ms/step step 20/938 [..............................] - loss: 0.0050 - acc: 0.9922 - ETA: 32s - 35ms/step @@ -791,10 +791,10 @@ step 330/938 [=========>....................] - loss: 0.0060 - acc: 0.9903 - ETA step 340/938 [=========>....................] - loss: 0.0587 - acc: 0.9903 - ETA: 16s - 27ms/step step 350/938 [==========>...................] - loss: 0.0434 - acc: 0.9904 - ETA: 16s - 27ms/step step 360/938 [==========>...................] - loss: 6.9384e-04 - acc: 0.9904 - ETA: 15s - 27ms/step -step 370/938 [==========>...................] - loss: 0.0134 - acc: 0.9904 - ETA: 15s - 27ms/step +step 370/938 [==========>...................] - loss: 0.0134 - acc: 0.9904 - ETA: 15s - 27ms/step step 380/938 [===========>..................] - loss: 0.0278 - acc: 0.9903 - ETA: 15s - 27ms/step step 390/938 [===========>..................] - loss: 5.5189e-04 - acc: 0.9902 - ETA: 14s - 27ms/step -step 400/938 [===========>..................] - loss: 0.0023 - acc: 0.9904 - ETA: 14s - 27ms/step +step 400/938 [===========>..................] - loss: 0.0023 - acc: 0.9904 - ETA: 14s - 27ms/step step 410/938 [============>.................] - loss: 0.0105 - acc: 0.9904 - ETA: 14s - 27ms/step step 420/938 [============>.................] - loss: 0.0398 - acc: 0.9901 - ETA: 14s - 27ms/step step 430/938 [============>.................] - loss: 0.0169 - acc: 0.9902 - ETA: 13s - 27ms/step @@ -811,7 +811,7 @@ step 530/938 [===============>..............] - loss: 0.0488 - acc: 0.9900 - ETA step 540/938 [================>.............] - loss: 0.0239 - acc: 0.9901 - ETA: 10s - 27ms/step step 550/938 [================>.............] - loss: 0.0303 - acc: 0.9900 - ETA: 10s - 27ms/step step 560/938 [================>.............] - loss: 0.0287 - acc: 0.9900 - ETA: 10s - 27ms/step -step 570/938 [=================>............] - loss: 0.0375 - acc: 0.9900 - ETA: 9s - 27ms/step +step 570/938 [=================>............] - loss: 0.0375 - acc: 0.9900 - ETA: 9s - 27ms/step step 580/938 [=================>............] - loss: 0.0197 - acc: 0.9900 - ETA: 9s - 27ms/step step 590/938 [=================>............] - loss: 0.0265 - acc: 0.9900 - ETA: 9s - 27ms/step step 600/938 [==================>...........] - loss: 0.0615 - acc: 0.9901 - ETA: 9s - 27ms/step @@ -819,7 +819,7 @@ step 610/938 [==================>...........] - loss: 0.0036 - acc: 0.9901 - ETA step 620/938 [==================>...........] - loss: 0.0079 - acc: 0.9900 - ETA: 8s - 27ms/step step 630/938 [===================>..........] - loss: 0.0071 - acc: 0.9901 - ETA: 8s - 27ms/step step 640/938 [===================>..........] - loss: 6.9407e-04 - acc: 0.9902 - ETA: 8s - 27ms/step -step 650/938 [===================>..........] - loss: 0.0024 - acc: 0.9902 - ETA: 7s - 27ms/step +step 650/938 [===================>..........] - loss: 0.0024 - acc: 0.9902 - ETA: 7s - 27ms/step step 660/938 [====================>.........] - loss: 0.0016 - acc: 0.9902 - ETA: 7s - 27ms/step step 670/938 [====================>.........] - loss: 0.0069 - acc: 0.9901 - ETA: 7s - 27ms/step step 680/938 [====================>.........] - loss: 0.0023 - acc: 0.9901 - ETA: 6s - 27ms/step @@ -848,7 +848,7 @@ step 900/938 [===========================>..] - loss: 0.0187 - acc: 0.9897 - ETA step 910/938 [============================>.] - loss: 0.0925 - acc: 0.9897 - ETA: 0s - 27ms/step step 920/938 [============================>.] - loss: 0.0317 - acc: 0.9898 - ETA: 0s - 27ms/step step 930/938 [============================>.] - loss: 0.0448 - acc: 0.9898 - ETA: 0s - 27ms/step -step 938/938 [==============================] - loss: 0.0140 - acc: 0.9897 - 27ms/step +step 938/938 [==============================] - loss: 0.0140 - acc: 0.9897 - 27ms/step Eval begin... step 10/157 [>.............................] - loss: 0.2273 - acc: 0.9828 - ETA: 1s - 12ms/step step 20/157 [==>...........................] - loss: 0.1525 - acc: 0.9773 - ETA: 1s - 11ms/step @@ -862,17 +862,17 @@ step 90/157 [================>.............] - loss: 0.0439 - acc: 0.9814 - ETA step 100/157 [==================>...........] - loss: 0.0033 - acc: 0.9828 - ETA: 0s - 10ms/step step 110/157 [====================>.........] - loss: 3.9403e-04 - acc: 0.9837 - ETA: 0s - 10ms/step step 120/157 [=====================>........] - loss: 6.5309e-04 - acc: 0.9846 - ETA: 0s - 10ms/step -step 130/157 [=======================>......] - loss: 0.0735 - acc: 0.9849 - ETA: 0s - 10ms/step +step 130/157 [=======================>......] - loss: 0.0735 - acc: 0.9849 - ETA: 0s - 10ms/step step 140/157 [=========================>....] - loss: 9.8257e-05 - acc: 0.9856 - ETA: 0s - 10ms/step -step 150/157 [===========================>..] - loss: 0.0412 - acc: 0.9859 - ETA: 0s - 10ms/step -step 157/157 [==============================] - loss: 2.9252e-04 - acc: 0.9860 - 10ms/step +step 150/157 [===========================>..] - loss: 0.0412 - acc: 0.9859 - ETA: 0s - 10ms/step +step 157/157 [==============================] - loss: 2.9252e-04 - acc: 0.9860 - 10ms/step Eval samples: 10000 ``` -​ 2、使用基础API结果 +​ 2、使用基础 API 结果 ```python -#附5次epoch +#附 5 次 epoch epoch: 0, batch_id: 0, loss is: [2.9994564], acc is: [0.0625] epoch: 0, batch_id: 300, loss is: [0.08384503], acc is: [0.96875] epoch: 0, batch_id: 600, loss is: [0.06951822], acc is: [0.984375] @@ -905,44 +905,44 @@ batch_id: 140, loss is: [0.07646853], acc is: [0.984375] Process finished with exit code 0 ``` -这部分简单说就是Paddle的高层API比基础API运行速度快,且简单好用,体验感较好。 +这部分简单说就是 Paddle 的高层 API 比基础 API 运行速度快,且简单好用,体验感较好。 -与Pytorch相比,Paddle文档中提供的代码下载不了数据集,需要手动下载。 +与 Pytorch 相比,Paddle 文档中提供的代码下载不了数据集,需要手动下载。 # 七、各种 trick 的用法 -PaddlePaddle有丰富的api可以实现各种调参trick,像dropout,batchnormalization,groupnormalization,l2regularization, lr decay等等都可以很轻松地实现。 -另外数据增强则推荐使用PIL库,尝试各种技巧不一定每次都能让模型准确度提升,毕竟训练神经网络是一个多参数配合的过程,只有练得多了才更容易找到最佳的方向。 +PaddlePaddle 有丰富的 api 可以实现各种调参 trick,像 dropout,batchnormalization,groupnormalization,l2regularization, lr decay 等等都可以很轻松地实现。 +另外数据增强则推荐使用 PIL 库,尝试各种技巧不一定每次都能让模型准确度提升,毕竟训练神经网络是一个多参数配合的过程,只有练得多了才更容易找到最佳的方向。 根据查阅资料,现总结以下几点: -1、 cuDNN操作的选择 -在 use_cudnn=True 时,框架底层调用的是cuDNN中的卷积操作。 -通常cuDNN库提供的操作具有很好的性能表现,其性能明显优于Paddle原生的CUDA实现,比如 conv2d 。 -但是cuDNN中有些操作的性能较差,比如: conv2d_transpose 在 batch_size=1 时、pool2d 在 global_pooling=True 时等, -这些情况下,cuDNN实现的性能差于Paddle的CUDA实现,建议手动设置 use_cudnn=False 。 +1、 cuDNN 操作的选择 +在 use_cudnn=True 时,框架底层调用的是 cuDNN 中的卷积操作。 +通常 cuDNN 库提供的操作具有很好的性能表现,其性能明显优于 Paddle 原生的 CUDA 实现,比如 conv2d 。 +但是 cuDNN 中有些操作的性能较差,比如: conv2d_transpose 在 batch_size=1 时、pool2d 在 global_pooling=True 时等, +这些情况下,cuDNN 实现的性能差于 Paddle 的 CUDA 实现,建议手动设置 use_cudnn=False 。 -2、使用融合功能的API -用户网络配置中使用融合功能的API,通常能取得更好的计算性能。 -例如softmax_with_cross_entropy通常会比softmax cross_entropy分开用好 +2、使用融合功能的 API +用户网络配置中使用融合功能的 API,通常能取得更好的计算性能。 +例如 softmax_with_cross_entropy 通常会比 softmax cross_entropy 分开用好 3、优化数据准备速度的方法 为降低训练的整体时间,建议用户使用异步数据读取的方式,并开启 use_double_buffer(默认开)。此外,用户可根据模型的实际情况设置数据队列的大小(capacity)。 -如果数据准备的时间大于模型执行的时间,或者出现了数据队列为空的情况,这时候需要考虑对Python的用户reader进行加速。常用的方法为:使用Python多进程准备数据。 -Python端的数据预处理,都是使用CPU完成。如果Paddle提供了相应功能的API,可将这部分预处理功能写到模型配置中,如此Paddle就可以使用GPU来完成该预处理功能, -这样也可以减轻CPU预处理数据的负担,提升总体训练速度。 +如果数据准备的时间大于模型执行的时间,或者出现了数据队列为空的情况,这时候需要考虑对 Python 的用户 reader 进行加速。常用的方法为:使用 Python 多进程准备数据。 +Python 端的数据预处理,都是使用 CPU 完成。如果 Paddle 提供了相应功能的 API,可将这部分预处理功能写到模型配置中,如此 Paddle 就可以使用 GPU 来完成该预处理功能, +这样也可以减轻 CPU 预处理数据的负担,提升总体训练速度。 4、显存优化策略 -GC(Garbage Collection)的原理是在网络运行阶段及时释放无用变量的显存空间,达到节省显存的目的。GC适用于使用Executor,ParallelExecutor做模型训练/预测的场合。 -由于原生的CUDA系统调用 cudaMalloc 和 cudaFree 均是同步操作,非常耗时。因此与许多框架类似,PaddlePaddle采用了显存预分配的策略加速显存分配。 +GC(Garbage Collection)的原理是在网络运行阶段及时释放无用变量的显存空间,达到节省显存的目的。GC 适用于使用 Executor,ParallelExecutor 做模型训练/预测的场合。 +由于原生的 CUDA 系统调用 cudaMalloc 和 cudaFree 均是同步操作,非常耗时。因此与许多框架类似,PaddlePaddle 采用了显存预分配的策略加速显存分配。 -5、Inplace策略 -原理是Op的输出复用Op输入的显存空间。 -由于目前设计上的一些问题,在开启Inplace策略后,必须保证后续exe.run中fetch_list的变量是persistable的。 -fetch_list:结果获取表,训练时一般有loss等。 +5、Inplace 策略 +原理是 Op 的输出复用 Op 输入的显存空间。 +由于目前设计上的一些问题,在开启 Inplace 策略后,必须保证后续 exe.run 中 fetch_list 的变量是 persistable 的。 +fetch_list:结果获取表,训练时一般有 loss 等。 推荐的最佳显存优化策略为: -开启Inplace策略:设置 build_strategy.enable_inplace = True ,并设置fetch_list中的 var.persistable = True 。 +开启 Inplace 策略:设置 build_strategy.enable_inplace = True ,并设置 fetch_list 中的 var.persistable = True 。 -PaddlePaddle在深度学习框架方面,已经覆盖了搜索、图像识别、语音语义识别理解、情感分析、机器翻译、用户画像推荐等多领域的业务和技术。 -基于动态图实现的AlexNet代码如下: +PaddlePaddle 在深度学习框架方面,已经覆盖了搜索、图像识别、语音语义识别理解、情感分析、机器翻译、用户画像推荐等多领域的业务和技术。 +基于动态图实现的 AlexNet 代码如下: ```python class ConvPoolLayer(nn.Layer): @@ -981,18 +981,18 @@ class ConvPoolLayer(nn.Layer): return x ``` -PaddlePaddle搭建cnn网络以及进行模型训练预测,可以说PaddlePaddle搭建训练pipeline还是比较方便的。 +PaddlePaddle 搭建 cnn 网络以及进行模型训练预测,可以说 PaddlePaddle 搭建训练 pipeline 还是比较方便的。 # 八、报错汇总 -1、 DataLoader报错问题 : +1、 DataLoader 报错问题 : ```python SystemError: (Fatal) Blocking queue is killed because the data reader raises an exception. [Hint: Expected killed_ != true, but received killed_:1 == true:1.] (at /paddle/paddle/fluid/operators/reader/blocking_queue.h:158) ``` -原因分析:由于PaddlePaddle和Pytorch两个框架在这部分并无区别,Paddle读取数据在这主要用到两个类:paddle.io.Dataset和paddle.io.DataLoader,所以查看源代码后发现在Dataset类中的__getitem__(self, idx)返回的数据不是numpy.ndarray类型 +原因分析:由于 PaddlePaddle 和 Pytorch 两个框架在这部分并无区别,Paddle 读取数据在这主要用到两个类:paddle.io.Dataset 和 paddle.io.DataLoader,所以查看源代码后发现在 Dataset 类中的__getitem__(self, idx)返回的数据不是 numpy.ndarray 类型 解决方案: @@ -1014,7 +1014,7 @@ class RandomDataset(Dataset): return self.num_samples ``` -注:还有一种情况是Dataset类的__getitem__(self, idx)返回的数据为字典(Dict) 类型也会报同样的错误,这时可把return改为return {'input': image, 'lb': label} +注:还有一种情况是 Dataset 类的__getitem__(self, idx)返回的数据为字典(Dict) 类型也会报同样的错误,这时可把 return 改为 return {'input': image, 'lb': label} @@ -1031,13 +1031,13 @@ SystemError: (Fatal) Blocking queue is killed because the data reader raises an [Hint: Expected killed != true, but received killed_:1 == true:1.] (at /paddle/paddle/fluid/operators/reader/blocking_queue.h:158) ``` -解决方案:由于自己数据集中有部分图片超过了默认shape的[3, 32, 320],图片宽度大于了320,所以直接删除或调大shape尺寸即可 +解决方案:由于自己数据集中有部分图片超过了默认 shape 的[3, 32, 320],图片宽度大于了 320,所以直接删除或调大 shape 尺寸即可 注:使用公开数据集时不会出现此问题 -3、使用paddle.reshape时出现错误 +3、使用 paddle.reshape 时出现错误 ```python ValueError: (InvalidArgument) The 'shape' in ReshapeOp is invalid. The input tensor X'size must be equal to the capacity of 'shape'. But received X's shape = [64, 50, 4, 4], X's size = 51200, 'shape' is [1, 800], the capacity of 'shape' is 800. @@ -1045,18 +1045,18 @@ ValueError: (InvalidArgument) The 'shape' in ReshapeOp is invalid. The input ten [operator < reshape2 > error] ``` -解决方案:在使用forward函数实现MNIST网络的执行逻辑时,忽略了self.pool_2_shape变量的大小,重新设置paddle.reshape为x = paddle.reshape(x, shape=[-1, self.pool_2_shape])即可 +解决方案:在使用 forward 函数实现 MNIST 网络的执行逻辑时,忽略了 self.pool_2_shape 变量的大小,重新设置 paddle.reshape 为 x = paddle.reshape(x, shape=[-1, self.pool_2_shape])即可 -4、tensor的值不能直接利用 +4、tensor 的值不能直接利用 -报错:paddlepaddle中的value不能直接拿出来用。 +报错:paddlepaddle 中的 value 不能直接拿出来用。 ```python TypeError: The type of 'shape' in reshape must be list[int] or tuple(int) in Dygraph mode, but received , which contains Variable. -#错误代码:其中stack_size, feat_size 为 tensor。 +#错误代码:其中 stack_size, feat_size 为 tensor。 #改进加入 # paddlepaddle code @@ -1066,7 +1066,7 @@ feat_size = feat_size.numpy() -5、Paddle加载数据集报错,无法下载MNIST数据集,需要手动进行下载,(使用了多台电脑测试,均会出现此情况) +5、Paddle 加载数据集报错,无法下载 MNIST 数据集,需要手动进行下载,(使用了多台电脑测试,均会出现此情况) ```python File "E:\anaconda\lib\site-packages\paddle\vision\datasets\mnist.py", line 98, in __init__ @@ -1088,8 +1088,8 @@ File "E:\anaconda\lib\site-packages\paddle\vision\datasets\cifar.py", line 122, RuntimeError: Cannot download https://dataset.bj.bcebos.com/cifar/cifar-10-python.tar.gz within retry limit 3 ``` -按照文档提供的'DatasetFolder', 'ImageFolder', 'MNIST', 'FashionMNIST', 'Flowers', 'Cifar10',多种数据集进行了下载测试,均无法在单机上加载数据集,需要手动下载数据集。 +按照文档提供的'DatasetFolder', 'ImageFolder', 'MNIST', 'FashionMNIST', 'Flowers', 'Cifar10',多种数据集进行了下载测试,均无法在单机上加载数据集,需要手动下载数据集。 - 且数据集的保存地址为一个缓存空间,用户在使用的时候可能找不到数据集,如/public/home/username/.cache/paddle/dataset目录。 + 且数据集的保存地址为一个缓存空间,用户在使用的时候可能找不到数据集,如/public/home/username/.cache/paddle/dataset 目录。 - 而pytorch的加载数据集API会把数据集加载到当前目录,这一点的体验要优于Paddle。 + 而 pytorch 的加载数据集 API 会把数据集加载到当前目录,这一点的体验要优于 Paddle。 diff --git a/docs/guides/cinn/basic_operator_cn.md b/docs/guides/cinn/basic_operator_cn.md index cd3821a01a1..16f59d6b39e 100644 --- a/docs/guides/cinn/basic_operator_cn.md +++ b/docs/guides/cinn/basic_operator_cn.md @@ -99,11 +99,11 @@ $$ * `sign`:对输入张量逐元素进行正负判断,并将各个位置的正负判断值保存到返回结果中。计算公式如下: $$ output = \begin{cases} - -1 & input \lt 0 \\ - -0 & input = -0 \\ - NaN & input = NaN \\ - +0 & input = +0 \\ - 1 & input \gt 0 + -1 & input \lt 0 \\ + -0 & input = -0 \\ + NaN & input = NaN \\ + +0 & input = +0 \\ + 1 & input \gt 0 \end{cases} $$ * `bitwise_not`:对输入张量逐元素按位取反,并将各个位置的输出元素保存到返回结果中。计算公式为$output = \sim input$。 diff --git a/docs/practices/reinforcement_learning/AlphaZero.ipynb b/docs/practices/reinforcement_learning/AlphaZero.ipynb index 8ceb320cee3..e7099dea991 100644 --- a/docs/practices/reinforcement_learning/AlphaZero.ipynb +++ b/docs/practices/reinforcement_learning/AlphaZero.ipynb @@ -1,1097 +1,1097 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "source": [ - "# 用飞桨框架2.0造一个会下五子棋的AI模型\n", - "\n", - "作者信息:yangguohao (https://github.com/yangguohao/)\n", - "\n", - "更新日期:2023 年 2 月 14 日\n", - "\n", - "## 1. 简要介绍\n", - "\n", - "### AlphaZero\n", - "\n", - "* [AlphaZero](https://arxiv.org/abs/1712.01815) 是由 Alphabet 旗下的子公司 DeepMind 研发的强化学习模型。 相较于之前的两代版本 [AlphaGo](https://www.nature.com/articles/nature16961) 和 [AlphaGoZero](https://www.nature.com/articles/nature24270),AlphaZero 完全无需人工特征、无需任何人类棋谱、甚至无需任何特定的策略和算法。\n", - "\n", - "* 作为一个通用模型,AlphaZero 不只针对围棋,而是同时学习了三种棋类-日本将棋、国际象棋以及围棋。从零开始,经过短时间的训练,AlphaZero 完胜各个领域里的最强AI。包括国际象棋的Stockfish、将棋的Elmo,以及围棋的前辈AlphaGo Zero。\n", - "\n", - "* 其核心主要为 蒙特卡洛树搜索(MCTS) 和 策略价值深度网络。通俗地说,蒙特卡洛树搜索能让 AlphaZero 多想几步棋,看的更远。而策略价值网络能让 AlphaZero 更准确地评估当前的棋局,提升蒙特卡洛树搜索的精度。两者相辅相成从而决定每一步的落子。\n", - "\n", - "![](https://ai-studio-static-online.cdn.bcebos.com/abb79e88765242ccb9df65f2cf4877fc83be041546bf47e293dcae1b98e7e8bb)\n", - "\n", - "\n", - "### 本项目简介\n", - "\n", - "* 本项目是AlphaZero算法的一个实现(使用PaddlePaddle框架),用于玩简单的棋盘游戏Gomoku(也称为五子棋),使用纯粹的自我博弈的方式开始训练。\n", - "\n", - "* Gomoku游戏比围棋或象棋简单得多,因此我们可以专注于AlphaZero的训练,在一台PC机上几个小时内就可以获得一个让你不可忽视的AI模型。\n", - "\n", - "![](https://ai-studio-static-online.cdn.bcebos.com/d510e461a8d84be3a1d0952874099910f4ac4da475e2424d862251d20f23c0f3)\n", - "\n", - "\n", - "* 因为和围棋相比,五子棋的规则较为简单,落子空间也比较小,因此没有用到AlphaGo Zero中大量使用的残差网络,只使用了卷积层和全连接层。本项目的网路简单,无需使用大量的计算就可以进行运行训练。使用的 Paddle 版本为 2.4.0。\n" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "## 2. 环境配置\n", - "本教程基于 PaddlePaddle 2.4.0 编写,如果你的环境不是本版本,请先参考官网安装 PaddlePaddle 2.4.0。" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [ - "!pip install pygame" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [ - "import random\n", - "import copy\n", - "import os\n", - "import time\n", - "from collections import defaultdict, deque\n", - "\n", - "import numpy as np\n", - "import paddle\n", - "import paddle.nn as nn \n", - "import paddle.nn.functional as F\n", - "import pygame\n", - "from pygame.locals import *\n" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "### 游戏环境\n", - "\n", - "初始化游戏环境,可以跳过该内容。\n", - "\n", - "主要是针对五子棋棋盘大小,走子以及游戏规则的设定,以及棋盘棋子等可视化的 UI 设定。" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [ - "\n", - "class Board(object):\n", - " \"\"\"棋盘游戏逻辑控制\"\"\"\n", - "\n", - " def __init__(self, **kwargs):\n", - " self.width = int(kwargs.get('width', 15)) # 棋盘宽度\n", - " self.height = int(kwargs.get('height', 15)) # 棋盘高度\n", - " self.states = {} # 棋盘状态为一个字典,键: 移动步数,值: 玩家的棋子类型\n", - " self.n_in_row = int(kwargs.get('n_in_row', 5)) # 5个棋子一条线则获胜\n", - " self.players = [1, 2] # 玩家1,2\n", - "\n", - " def init_board(self, start_player=0):\n", - " # 初始化棋盘\n", - "\n", - " # 当前棋盘的宽高小于5时,抛出异常(因为是五子棋)\n", - " if self.width < self.n_in_row or self.height < self.n_in_row:\n", - " raise Exception('棋盘的长宽不能少于{}'.format(self.n_in_row))\n", - " self.current_player = self.players[start_player] # 先手玩家\n", - " self.availables = list(range(self.width * self.height)) # 初始化可用的位置列表\n", - " self.states = {} # 初始化棋盘状态\n", - " self.last_move = -1 # 初始化最后一次的移动位置\n", - "\n", - " def current_state(self):\n", - " \"\"\"\n", - " 从当前玩家的角度返回棋盘状态。\n", - " 状态形式: 4 * 宽 * 高\n", - " \"\"\"\n", - " # 使用4个15x15的二值特征平面来描述当前的局面\n", - " # 前两个平面分别表示当前player的棋子位置和对手player的棋子位置,有棋子的位置是1,没棋子的位置是0\n", - " # 第三个平面表示对手player最近一步的落子位置,也就是整个平面只有一个位置是1,其余全部是0\n", - " # 第四个平面表示的是当前player是不是先手player,如果是先手player则整个平面全部为1,否则全部为0\n", - " square_state = np.zeros((4, self.width, self.height))\n", - " if self.states:\n", - " moves, players = np.array(list(zip(*self.states.items())))\n", - " move_curr = moves[players == self.current_player] # 获取棋盘状态上属于当前玩家的所有移动值\n", - " move_oppo = moves[players != self.current_player] # 获取棋盘状态上属于对方玩家的所有移动值\n", - " square_state[0][move_curr // self.width, # 对第一个特征平面填充值(当前玩家)\n", - " move_curr % self.height] = 1.0\n", - " square_state[1][move_oppo // self.width, # 对第二个特征平面填充值(对方玩家)\n", - " move_oppo % self.height] = 1.0\n", - " # 指出最后一个移动位置\n", - " square_state[2][self.last_move // self.width, # 对第三个特征平面填充值(对手最近一次的落子位置)\n", - " self.last_move % self.height] = 1.0\n", - " if len(self.states) % 2 == 0: # 对第四个特征平面填充值,当前玩家是先手,则填充全1,否则为全0\n", - " square_state[3][:, :] = 1.0\n", - " # 将每个平面棋盘状态按行逆序转换(第一行换到最后一行,第二行换到倒数第二行..)\n", - " return square_state[:, ::-1, :]\n", - "\n", - " def do_move(self, move):\n", - " # 根据移动的数据更新各参数\n", - " self.states[move] = self.current_player # 将当前的参数存入棋盘状态中\n", - " self.availables.remove(move) # 从可用的棋盘列表移除当前移动的位置\n", - " self.current_player = (\n", - " self.players[0] if self.current_player == self.players[1]\n", - " else self.players[1]\n", - " ) # 改变当前玩家\n", - " self.last_move = move # 记录最后一次的移动位置\n", - "\n", - " def has_a_winner(self):\n", - " # 是否产生赢家\n", - " width = self.width # 棋盘宽度\n", - " height = self.height # 棋盘高度\n", - " states = self.states # 状态\n", - " n = self.n_in_row # 获胜需要的棋子数量\n", - "\n", - " # 当前棋盘上所有的落子位置\n", - " moved = list(set(range(width * height)) - set(self.availables))\n", - " if len(moved) < self.n_in_row + 2:\n", - " # 当前棋盘落子数在7个以上时会产生赢家,落子数低于7个时,直接返回没有赢家\n", - " return False, -1\n", - "\n", - " # 遍历落子数\n", - " for m in moved:\n", - " h = m // width\n", - " w = m % width # 获得棋子的坐标\n", - " player = states[m] # 根据移动的点确认玩家\n", - "\n", - " # 判断各种赢棋的情况\n", - " # 横向5个\n", - " if (w in range(width - n + 1) and\n", - " len(set(states.get(i, -1) for i in range(m, m + n))) == 1):\n", - " return True, player\n", - "\n", - " # 纵向5个\n", - " if (h in range(height - n + 1) and\n", - " len(set(states.get(i, -1) for i in range(m, m + n * width, width))) == 1):\n", - " return True, player\n", - "\n", - " # 左上到右下斜向5个\n", - " if (w in range(width - n + 1) and h in range(height - n + 1) and\n", - " len(set(states.get(i, -1) for i in range(m, m + n * (width + 1), width + 1))) == 1):\n", - " return True, player\n", - "\n", - " # 右上到左下斜向5个\n", - " if (w in range(n - 1, width) and h in range(height - n + 1) and\n", - " len(set(states.get(i, -1) for i in range(m, m + n * (width - 1), width - 1))) == 1):\n", - " return True, player\n", - "\n", - " # 当前都没有赢家,返回False\n", - " return False, -1\n", - "\n", - " def game_end(self):\n", - " \"\"\"检查当前棋局是否结束\"\"\"\n", - " win, winner = self.has_a_winner()\n", - " if win:\n", - " return True, winner\n", - " elif not len(self.availables):\n", - " # 棋局布满,没有赢家\n", - " return True, -1\n", - " return False, -1\n", - "\n", - " def get_current_player(self):\n", - " return self.current_player\n", - "\n", - "\n", - "# 加上UI的布局的训练方式\n", - "class Game_UI(object):\n", - " \"\"\"游戏控制区域\"\"\"\n", - "\n", - " def __init__(self, board, **kwargs):\n", - " self.board = board # 加载棋盘控制类\n", - "\n", - " # 初始化 pygame\n", - " pygame.init()\n", - "\n", - " def start_play_evaluate(self, player1, player2, start_player=0):\n", - " \"\"\"开始一局游戏,评估当前的价值策略网络的胜率\"\"\"\n", - " if start_player not in (0, 1):\n", - " # 如果玩家不在玩家1,玩家2之间,抛出异常\n", - " raise Exception('开始的玩家必须为0(玩家1)或1(玩家2)')\n", - " self.board.init_board(start_player) # 初始化棋盘\n", - " p1, p2 = self.board.players # 加载玩家1,玩家2\n", - " player1.set_player_ind(p1) # 设置玩家1\n", - " player2.set_player_ind(p2) # 设置玩家2\n", - " players = {p1: player1, p2: player2}\n", - " \n", - " while True:\n", - "\n", - " current_player = self.board.current_player # 获取当前玩家\n", - " player_in_turn = players[current_player] # 当前玩家的信息\n", - " move = player_in_turn.get_action(self.board) # 基于MCTS的AI下一步落子\n", - " self.board.do_move(move) # 根据下一步落子的状态更新棋盘各参数\n", - " \n", - " # 判断当前棋局是否结束\n", - " end, winner = self.board.game_end()\n", - " # 结束\n", - " if end:\n", - " win = winner\n", - " break\n", - " \n", - " return win\n", - "\n", - " def start_play_train(self, player, temp=1e-3):\n", - " \"\"\" \n", - " 开始自我博弈,使用MCTS玩家开始自己玩游戏,重新使用搜索树并存储自己玩游戏的数据\n", - " (state, mcts_probs, z) 提供训练\n", - " \"\"\"\n", - " self.board.init_board() # 初始化棋盘\n", - " states, mcts_probs, current_players = [], [], [] # 状态,mcts的行为概率,当前玩家\n", - "\n", - " while True:\n", - "\n", - " # 根据当前棋盘状态返回可能得行为,及行为对应的概率\n", - " move, move_probs = player.get_action(self.board,\n", - " temp=temp,\n", - " return_prob=1)\n", - " # 存储数据\n", - " states.append(self.board.current_state()) # 存储状态数据\n", - " mcts_probs.append(move_probs) # 存储行为概率数据\n", - " current_players.append(self.board.current_player) # 存储当前玩家\n", - " # 执行一个移动\n", - " self.board.do_move(move)\n", - "\n", - " # 判断该局游戏是否终止\n", - " end, winner = self.board.game_end()\n", - " if end:\n", - " # 从每个状态的当时的玩家的角度看待赢家\n", - " winners_z = np.zeros(len(current_players))\n", - " if winner != -1:\n", - " # 没有赢家时\n", - " winners_z[np.array(current_players) == winner] = 1.0\n", - " winners_z[np.array(current_players) != winner] = -1.0\n", - " # 重置MSCT的根节点\n", - " player.reset_player()\n", - " return winner, zip(states, mcts_probs, winners_z)\n" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "## 3. 价值策略网络\n", - "\n", - "* 原论文中的策略价值网络的结构是一个 CNN 组成的神经网络,初始游戏状态的张量在经过一个基本的卷积后,使用了19层或者39层的深度残差网络,最后输出价值和策略两个部分。\n", - "\n", - "![](https://ai-studio-static-online.cdn.bcebos.com/ac1bbf4b83b04a1d8f7e0abee8ae51fc91725b3526ac440da643a2f8d82b28f9)\n", - "\n", - "* 而这里为了演示算法,网络并不复杂,深度较浅。但是整体的逻辑与 AlphaZero 相似,由公共网络层、行动策略网络层和状态价值网络层构成。公共网络层使用卷积网络对棋盘上的状态进行特征提取,而行动策略层用以输出每个可落子点的落子概率,状态价值层用以输出可落子点的价值的评分。" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [ - "class Net(paddle.nn.Layer):\n", - " def __init__(self,board_width, board_height):\n", - " super(Net, self).__init__()\n", - " self.board_width = board_width\n", - " self.board_height = board_height\n", - " # 公共网络层\n", - " self.conv1 = nn.Conv2D(in_channels=4,out_channels=32,kernel_size=3,padding=1)\n", - " self.conv2 = nn.Conv2D(in_channels=32,out_channels=64,kernel_size=3,padding=1)\n", - " self.conv3 = nn.Conv2D(in_channels=64,out_channels=128,kernel_size=3,padding=1)\n", - " # 行动策略网络层\n", - " self.act_conv1 = nn.Conv2D(in_channels=128,out_channels=4,kernel_size=1,padding=0)\n", - " self.act_fc1 = nn.Linear(4*self.board_width*self.board_height,\n", - " self.board_width*self.board_height)\n", - " self.val_conv1 = nn.Conv2D(in_channels=128,out_channels=2,kernel_size=1,padding=0)\n", - " self.val_fc1 = nn.Linear(2*self.board_width*self.board_height, 64)\n", - " self.val_fc2 = nn.Linear(64, 1)\n", - "\n", - " def forward(self, inputs):\n", - " # 公共网络层 \n", - " x = F.relu(self.conv1(inputs))\n", - " x = F.relu(self.conv2(x))\n", - " x = F.relu(self.conv3(x))\n", - " # 行动策略网络层\n", - " x_act = F.relu(self.act_conv1(x))\n", - " x_act = paddle.reshape(\n", - " x_act, [-1, 4 * self.board_height * self.board_width])\n", - " \n", - " x_act = F.log_softmax(self.act_fc1(x_act)) \n", - " # 状态价值网络层\n", - " x_val = F.relu(self.val_conv1(x))\n", - " x_val = paddle.reshape(\n", - " x_val, [-1, 2 * self.board_height * self.board_width])\n", - " x_val = F.relu(self.val_fc1(x_val))\n", - " x_val = F.tanh(self.val_fc2(x_val))\n", - "\n", - " return x_act,x_val" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "* 在定义好策略和价值网络的基础上,接下来实现PolicyValueNet类,该类主要定义:policy_value_fn()方法,主要用于蒙特卡洛树搜索时评估叶子节点对应局面评分、该局所有可行动作及对应概率;另一个方法train_step(),主要用于更新自我对弈收集数据上策略价值网络的参数。\n", - "\n", - "* 在训练神经网络阶段,我们使用自我对战学习阶段得到的样本集合 (s,π,z) 来训练我们神经网络的模型参数。训练的目的是对于每个输入的棋盘状态 s, 神经网络输出的概率 p 和价值 v 和我们训练样本中的 π,z 差距尽可能的少。\n", - "\n", - "* 损失函数由三部分组成,\n", - " * 第一部分是均方误差损失函数,对应代码中 value_loss,用于评估神经网络预测的胜负结果和真实结果之间的差异。\n", - " * 第二部分是交叉熵损失函数,对应 policy_loss,用于评估神经网络的输出策略和我们 MCTS 输出的策略的差异。\n", - " * 第三部分是L2正则化项,对应优化器 self.optimizer 中的 weight_decay, 用于控制网络模型的复杂度。" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [ - "\n", - "class PolicyValueNet():\n", - " \"\"\"策略&值网络 \"\"\"\n", - " def __init__(self, board_width, board_height,\n", - " model_file=None, use_gpu=True):\n", - " self.use_gpu = use_gpu\n", - " self.board_width = board_width\n", - " self.board_height = board_height\n", - " self.l2_const = 1e-3 # coef of l2 penalty\n", - " \n", - "\n", - " self.policy_value_net = Net(self.board_width, self.board_height) \n", - " \n", - " self.optimizer = paddle.optimizer.Adam(learning_rate=0.02,\n", - " parameters=self.policy_value_net.parameters(), weight_decay=self.l2_const)\n", - " \n", - "\n", - " if model_file:\n", - " net_params = paddle.load(model_file)\n", - " self.policy_value_net.set_state_dict(net_params)\n", - " \n", - " def policy_value_evaluate(self, state_batch):\n", - " \"\"\"\n", - " 评估函数\n", - " Args:\n", - " input: 一组棋盘状态\n", - " output: 根据棋盘状态输出对应的动作概率及价值\n", - " \"\"\"\n", - " state_batch = paddle.to_tensor(state_batch)\n", - " log_act_probs, value = self.policy_value_net(state_batch)\n", - " act_probs = np.exp(log_act_probs.numpy())\n", - " return act_probs, value.numpy()\n", - "\n", - " def policy_value_fn(self, board):\n", - " \"\"\"\n", - " 评估场面局势,给出每个位置的概率及价值\n", - " Args:\n", - " input: 棋盘状态\n", - " output: 返回一组列表,包含棋盘每个可下的点的动作概率以及价值得分。\n", - " \"\"\"\n", - " legal_positions = board.availables\n", - " current_state = np.ascontiguousarray(board.current_state().reshape(\n", - " -1, 4, self.board_width, self.board_height)).astype(\"float32\")\n", - "\n", - " act_probs, value = self.policy_value_evaluate(current_state)\n", - " act_probs = zip(legal_positions, act_probs.flatten()[legal_positions])\n", - " return act_probs, value\n", - "\n", - " def train_step(self, state_batch, mcts_probs, winner_batch, lr=0.002):\n", - " \"\"\"用采样得到的样本集合对策略价值网络进行一次训练\"\"\"\n", - " # wrap in Tensor\n", - " state_batch = paddle.to_tensor(state_batch)\n", - " mcts_probs = paddle.to_tensor(mcts_probs)\n", - " winner_batch = paddle.to_tensor(winner_batch)\n", - "\n", - " # zero the parameter gradients\n", - " self.optimizer.clear_gradients()\n", - " # set learning rate\n", - " self.optimizer.set_lr(lr)\n", - "\n", - " # forward\n", - " log_act_probs, value = self.policy_value_net(state_batch)\n", - " # define the loss = (z - v)^2 - pi^T * log(p) + c||theta||^2\n", - " # Note: the L2 penalty is incorporated in optimizer\n", - " value = paddle.reshape(x=value, shape=[-1])\n", - " value_loss = F.mse_loss(input=value, label=winner_batch)\n", - " policy_loss = -paddle.mean(paddle.sum(mcts_probs*log_act_probs, axis=1))\n", - " loss = value_loss + policy_loss\n", - " # backward and optimize\n", - " loss.backward()\n", - " self.optimizer.minimize(loss)\n", - " return loss.numpy()\n", - "\n", - " def get_policy_param(self):\n", - " net_params = self.policy_value_net.state_dict()\n", - " return net_params\n", - "\n", - " def save_model(self, model_file):\n", - " \"\"\"保存模型\"\"\"\n", - " net_params = self.get_policy_param() # get model params\n", - " paddle.save(net_params, model_file)" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "## 4. 蒙特卡洛树搜索(MCTS)\n", - "\n", - "传统的AI博弈树搜索算法效率都很低,因为这些算法在做出最终选择前需要穷尽每一种走法。即便很少的分支因子的游戏,其每一步的搜索空间也会爆炸式增长。分支因子就是所有可能的走法的数量,这个数量会随着游戏的进行不断变化。因此,你可以试着计算一个游戏的平均分支因子数,国际象棋的平均分支因子是35,而围棋则是250。这意味着,在国际象棋中,仅走两步就有1,225(35²)种可能的棋面,而在围棋中,这个数字会变成62,500(250²)。因此,上述的价值策略神经网络将指导并告诉我们哪些博弈路径值得探索,从而避免被许多无用的搜索路径所淹没。再结合蒙特卡洛树选择最佳的走法。\n", - "\n", - "### 棋类游戏的蒙特卡洛树搜索(MCTS)\n", - "使用MCTS的具体做法是这样的,给定一个棋面,MCTS共进行N次模拟。主要的搜索阶段有4个:选择,扩展,仿真和回溯\n", - "\n", - "![](https://ai-studio-static-online.cdn.bcebos.com/73384055df364b44a49e7e206a9015790be7b3c0aa1942d0a4e57aa617fad087)\n", - "\n", - "* 第一步是选择(Selection),这一步会从根节点开始,每次都选一个“最值得搜索的子节点”,一般使用上限置信区间算法 (Upper Confidence Bound Apply to Tree, UCT) 选择分数最高的节点,直到来到一个“存在未扩展的子节点”的节点\n", - "\n", - "* 第二步是扩展(Expansion),在这个搜索到的“存在未扩展的子节点”之上,加上一个没有历史记录的子节点并初始化该子节点\n", - "\n", - "* 第三步是仿真(simulation),从上面这个没有试过的着法开始,用一个简单策略比如快速走子策略 (Rollout policy) 走到底,得到一个胜负结果。快速走子策略虽然不是很精确,但是速度较快,在这里具有优势。因为如果这个策略走得慢,结果虽然会更准确,但由于耗时多了,在单位时间内的模拟次数就少了,所以不一定会棋力更强,有可能会更弱。这也是为什么我们一般只模拟一次,因为如果模拟多次,虽然更准确,但更慢。\n", - "\n", - "* 第四步是回溯 (backpropagation), 将我们最后得到的胜负结果回溯加到MCTS树结构上。注意除了之前的MCTS树要回溯外,新加入的节点也要加上一次胜负历史记录。\n", - "\n", - "以上就是MCTS搜索的整个过程。这4步一般是通用的,但是MCTS树结构上保存的内容而一般根据要解决的问题和建模的复杂度而不同。\n", - "### 基于神经网络的蒙特卡洛树搜索(MCTS)\n", - "N(s,a) :记录边的访问次数;\n", - "W(s,a): 合计行动价值;\n", - "Q(s,a) :平均行动价值;\n", - "P(s,a) :选择该条边的先验概率;\n", - "* 首先是选择(Selection):在MCTS内部,出现过的局面,我们会使用UCT选择子分支。最终我们会选择Q+U最大的子分支作为搜索分支,一直走到棋局结束,或者走到了没有到终局MCTS的叶子节点。$c_{puct}$是决定探索程度的一个系数\n", - "\n", - "![](https://ai-studio-static-online.cdn.bcebos.com/a3edc34d8d554068becbfb21b7f6a5f7fc0b43f804eb45ed9c92a27d38478fdd)\n", - "\n", - "* 然后是扩展(Expansion)和仿真(simulation):对于叶子节点状态s,会利用神经网络对叶子节点做预测,得到当前叶子节点的各个可能的子节点位置sL落子的概率p和对应的价值v,对于这些可能的新节点我们在MCTS中创建出来,初始化其分支上保存的信息为\n", - "\n", - "![](https://ai-studio-static-online.cdn.bcebos.com/aed60f9babbb4c208f19d480fd25558b903c4836a2e5438bb858e6ddcaa218c9)\n", - "\n", - "\n", - "* 最后是回溯(backpropagation):将新叶子节点分支的信息回溯累加到祖先节点分支上去。这个回溯的逻辑也是很简单的,从每个叶子节点L依次向根节点回溯,并依次更新上层分支数据结构如下:\n", - "\n", - "![](https://ai-studio-static-online.cdn.bcebos.com/b39e2bd4ab0f42a691e6a239443bc57f0dfac929a03b428b8a06b0416d4340c3)\n", - "\n", - "MCTS搜索完毕后,模型就可以在MCTS的根节点s基于以下公式选择行棋的MCTS分支了:\n", - "\n", - "![](https://ai-studio-static-online.cdn.bcebos.com/3a14cd6be857468b9bcbcdee61d6ecdb325a864649284df4a8aa5b1d2b7605a0)\n", - "\n", - "τ是用来控制探索的程度,τ的取值介于(0,1]之间,当τ越接近于1时,神经网络的采样越接近于MCTS的原始采样,当τ越接近于0时,神经网络的采样越接近于贪婪策略,即选择最大访问次数N所对应的动作。\n", - "因为在τ很小的情况下,直接计算访问次数N的τ次方根可能会导致数值异常,为了避免这种情况,在计算行动概率时,先将访问次数N加上一个非常小的数值(本项目是1e-10),取自然对数后乘上1/τ,再用一个简化的softmax函数将输出还原为概率,这和原始公式在数学上基本上是等效的。\n", - "\n", - "**关键点是什么?**\n", - "* 通过每一次模拟,MCTS依靠神经网络, 使用累计价值 (Q)、神经网络给出的走法先验概率 (P) 以及访问对应节点的频率这些数字的组合,沿着最有希望获胜的路径(换句话说,也就是具有最高置信区间上界的路径)进行探索。\n", - "\n", - "* 在每一次模拟中,MCTS会尽可能向纵深进行探索直至遇到它从未见过的盘面状态,在这种情况下,它会通过神经网络来评估该盘面状态的优劣。\n", - "\n", - "* 巧妙了使用MCTS搜索树和神经网络一起,通过MCTS搜索树优化神经网络参数,反过来又通过优化的神经网络指导MCTS搜索。\n", - "\n", - "\n" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [ - "\n", - "def softmax(x):\n", - " probs = np.exp(x - np.max(x))\n", - " probs /= np.sum(probs)\n", - " return probs\n", - "\n", - "\n", - "def policy_value_fn(board):\n", - " \"\"\"\n", - " 接受状态并输出(动作,概率)列表的函数元组和状态的分数\"\"\"\n", - " # 返回统一概率和0分的纯MCTS\n", - " action_probs = np.ones(len(board.availables)) / len(board.availables)\n", - " return zip(board.availables, action_probs), 0\n", - "\n", - "\n", - "class TreeNode(object):\n", - " \"\"\"MCTS树中的节点。\n", - "\n", - " 每个节点跟踪其自身的值Q,先验概率P及其访问次数调整的先前得分u。\n", - " \"\"\"\n", - "\n", - " def __init__(self, parent, prior_p):\n", - " self._parent = parent\n", - " self._children = {} # 从动作到TreeNode的映射\n", - " self._n_visits = 0\n", - " self._Q = 0\n", - " self._u = 0\n", - " self._P = prior_p\n", - "\n", - " def expand(self, action_priors):\n", - " \"\"\"通过创建新子项来展开树。\n", - " action_priors:一系列动作元组及其先验概率根据策略函数.\n", - " \"\"\"\n", - " for action, prob in action_priors:\n", - " if action not in self._children:\n", - " self._children[action] = TreeNode(self, prob)\n", - "\n", - " def select(self, c_puct):\n", - " \"\"\"在子节点中选择能够提供最大行动价值Q的行动加上奖金u(P)。\n", - " return:(action,next_node)的元组\n", - " \"\"\"\n", - " return max(self._children.items(),\n", - " key=lambda act_node: act_node[1].get_value(c_puct))\n", - "\n", - " def update(self, leaf_value):\n", - " \"\"\"从叶节点评估中更新节点值\n", - " leaf_value: 这个子树的评估值来自从当前玩家的视角\n", - " \"\"\"\n", - " # 统计访问次数\n", - " self._n_visits += 1\n", - " # 更新Q值,取对于所有访问次数的平均数\n", - " self._Q += 1.0 * (leaf_value - self._Q) / self._n_visits\n", - "\n", - " def update_recursive(self, leaf_value):\n", - " \"\"\"就像调用update()一样,但是对所有祖先进行递归应用。\n", - " \"\"\"\n", - " # 如果它不是根节点,则应首先更新此节点的父节点。\n", - " if self._parent:\n", - " self._parent.update_recursive(-leaf_value)\n", - " self.update(leaf_value)\n", - "\n", - " def get_value(self, c_puct):\n", - " \"\"\"计算并返回此节点的值。它是叶评估Q和此节点的先验的组合\n", - " 调整了访问次数,u。\n", - " c_puct:控制相对影响的(0,inf)中的数字,该节点得分的值Q和先验概率P.\n", - " \"\"\"\n", - " self._u = (c_puct * self._P *\n", - " np.sqrt(self._parent._n_visits) / (1 + self._n_visits))\n", - " return self._Q + self._u\n", - "\n", - " def is_leaf(self):\n", - " \"\"\"检查叶节点(即没有扩展的节点)。\"\"\"\n", - " return self._children == {}\n", - "\n", - " def is_root(self):\n", - " return self._parent is None\n", - "\n", - "\n", - "class MCTS(object):\n", - " \"\"\"对蒙特卡罗树搜索的一个简单实现\"\"\"\n", - "\n", - " def __init__(self, policy_value_fn, c_puct=5, n_playout=10000, mode='train'):\n", - " \"\"\"\n", - " policy_value_fn:一个接收板状态和输出的函数(动作,概率)元组列表以及[-1,1]中的分数\n", - " (即来自当前的最终比赛得分的预期值玩家的观点)对于当前的玩家。\n", - " c_puct:(0,inf)中的数字,用于控制探索的速度收敛于最大值政策。 更高的价值意味着\n", - " 依靠先前的更多。\n", - " \"\"\"\n", - " self._root = TreeNode(None, 1.0)\n", - " self._policy = policy_value_fn\n", - " self._c_puct = c_puct\n", - " self._n_playout = n_playout\n", - " self.mode = mode\n", - "\n", - " def _playout(self, state):\n", - " \"\"\"从根到叶子运行单个播出,获取值\n", - " 叶子并通过它的父母传播回来。\n", - " State已就地修改,因此必须提供副本。\n", - " \"\"\"\n", - " node = self._root\n", - " while True:\n", - " if node.is_leaf():\n", - " break\n", - " # 贪心算法选择下一步行动\n", - " action, node = node.select(self._c_puct)\n", - " state.do_move(action)\n", - "\n", - " # 使用网络评估叶子,该网络输出(动作,概率)元组p的列表以及当前玩家的[-1,1]中的分数v。\n", - " action_probs, leaf_value = self._policy(state)\n", - " # 查看游戏是否结束\n", - " end, winner = state.game_end()\n", - " if not end:\n", - " node.expand(action_probs)\n", - " if self.mode == 'train':\n", - " if end:\n", - " # 对于结束状态,将叶子节点的值换成\"true\"\n", - " if winner == -1: # tie\n", - " leaf_value = 0.0\n", - " else:\n", - " leaf_value = (\n", - " 1.0 if winner == state.get_current_player() else -1.0\n", - " )\n", - " else:\n", - " # 通过随机的rollout评估叶子结点\n", - " leaf_value = self._evaluate_rollout(state)\n", - " # 在本次遍历中更新节点的值和访问次数\n", - " node.update_recursive(-leaf_value)\n", - "\n", - " @staticmethod\n", - " def _evaluate_rollout(state, limit=1000):\n", - " \"\"\"使用推出策略直到游戏结束,\n", - " 如果当前玩家获胜则返回+1,如果对手获胜则返回-1,\n", - " 如果是平局则为0。\n", - " \"\"\"\n", - " player = state.get_current_player()\n", - " winner = -1\n", - " for i in range(limit):\n", - " end, winner = state.game_end()\n", - " if end:\n", - " break\n", - " max_action = np.random.choice(board.availables)\n", - " state.do_move(max_action)\n", - " else:\n", - " # 如果没有从循环中断,请发出警告。\n", - " print(\"WARNING: rollout reached move limit\")\n", - " if winner == -1: # tie\n", - " return 0\n", - " else:\n", - " return 1 if winner == player else -1\n", - "\n", - " def get_move(self, state, temp=1e-3):\n", - " \"\"\"\n", - " 如果 prob 为 True,则按顺序运行所有播出并返回可用的操作及其相应的概率。\n", - " 否则按顺序运行所有播出并返回访问量最大的操作。\n", - " \"\"\"\n", - " for n in range(self._n_playout):\n", - " state_copy = copy.deepcopy(state)\n", - " self._playout(state_copy)\n", - " if self.mode == 'train':\n", - " # 根据根节点处的访问计数来计算移动概率\n", - " act_visits = [(act, node._n_visits)\n", - " for act, node in self._root._children.items()]\n", - " acts, visits = zip(*act_visits)\n", - " act_probs = softmax(1.0 / temp * np.log(np.array(visits) + 1e-10))\n", - "\n", - " return acts, act_probs\n", - "\n", - " return max(self._root._children.items(),\n", - " key=lambda act_node: act_node[1]._n_visits)[0]\n", - "\n", - " def update_with_move(self, last_move):\n", - " \"\"\"保留我们已经知道的关于子树的信息\n", - " \"\"\"\n", - " if last_move in self._root._children:\n", - " self._root = self._root._children[last_move]\n", - " self._root._parent = None\n", - " else:\n", - " self._root = TreeNode(None, 1.0)\n", - "\n", - " def __str__(self):\n", - " return \"MCTS\"\n", - "\n", - "\n", - "class MCTSPlayer(object):\n", - " \"\"\"基于MCTS的AI玩家\"\"\"\n", - "\n", - " def __init__(self, policy_value_function=policy_value_fn,\n", - " c_puct=5, n_playout=2000, is_selfplay=0, mode='train'):\n", - " self.mcts = MCTS(policy_value_function, c_puct, n_playout, mode)\n", - " self._is_selfplay = is_selfplay\n", - "\n", - " def set_player_ind(self, p):\n", - " self.player = p\n", - "\n", - " def reset_player(self):\n", - " self.mcts.update_with_move(-1)\n", - "\n", - " def get_action(self, board, temp=1e-3, return_prob=0):\n", - "\n", - " sensible_moves = board.availables\n", - " # 像alphaGo Zero论文一样使用MCTS算法返回的pi向量\n", - " move_probs = np.zeros(board.width * board.height)\n", - " if len(sensible_moves) > 0:\n", - " if self.mcts.mode == 'train':\n", - " acts, probs = self.mcts.get_move(board, temp)\n", - " move_probs[list(acts)] = probs\n", - " if self._is_selfplay:\n", - " # 添加Dirichlet Noise进行探索(自我训练所需)\n", - " move = np.random.choice(\n", - " acts,\n", - " p=0.75 * probs + 0.25 * np.random.dirichlet(0.3 * np.ones(len(probs)))\n", - " )\n", - " # 更新根节点并重用搜索树\n", - " self.mcts.update_with_move(move)\n", - " else:\n", - " # 使用默认的temp = 1e-3,它几乎相当于选择具有最高概率的移动\n", - " move = np.random.choice(acts, p=probs)\n", - " # 重置根节点\n", - " self.mcts.update_with_move(-1)\n", - "\n", - " if return_prob:\n", - " return move, move_probs\n", - " else:\n", - " return move\n", - " else:\n", - " move = self.mcts.get_move(board)\n", - " self.mcts.update_with_move(-1)\n", - " return move\n", - " else:\n", - " print(\"棋盘已满\")\n", - "\n", - " def __str__(self):\n", - " return \"MCTS {}\".format(self.player)\n" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } - }, - { - "cell_type": "markdown", - "source": [ - "## 5. 模型训练\n", - "\n", - "* AlphaZero的算法流程,概括来说就是通过自我对弈收集数据,并用于更新策略价值网络,更新后的策略价值网络又会被用于后续的自我对弈过程中,从而产生高质量的自我对弈数据,这样相互促进、不断迭代,实现稳定的学习和提升。\n", - "\n", - "* 为了加快训练设置棋盘大小为 6×6,自我对弈500轮。\n", - "\n", - "### 模型训练过程以及伪代码\n", - "\n", - "每轮训练过程中,首先使用上文中提到的蒙特卡洛树搜索及策略价值网络进行一场自我对弈。\n", - "\n", - "在一场自我对弈中,每一手棋都通过蒙特卡洛树的四个步骤搜索走法,并得到最终落子的策略概率 Π。 在自我对弈最终结束时,标记胜负,并将其中每一手棋的状态概率以及胜负放入经验池中。\n", - "\n", - "最后通过对经验池采样,训练更新策略价值网络。\n", - "\n", - "下面给出图示及论文中的伪代码\n", - "\n", - "#### 图示\n", - "![](https://ai-studio-static-online.cdn.bcebos.com/0841d0771e704488bdbb989e1bdabbe2f82610f471a84a83b48826c35c5da23f)\n", - "\n", - "\n", - "#### 伪代码\n", - "![](https://ai-studio-static-online.cdn.bcebos.com/f306ff233cd746208896826bc0ca2f3e2d442b7187fd4fb5815f6b5e688f74ff)\n" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%% md\n" - } - } - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "execution": { - "iopub.execute_input": "2022-12-30T01:20:46.858489Z", - "iopub.status.busy": "2022-12-30T01:20:46.857980Z", - "iopub.status.idle": "2022-12-30T04:19:33.342924Z", - "shell.execute_reply": "2022-12-30T04:19:33.341753Z", - "shell.execute_reply.started": "2022-12-30T01:20:46.858457Z" - }, - "scrolled": true, - "tags": [], - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "batch i:50, episode_len:18, loss :[3.2428253], entropy:2.993258476257324\r\n", - "batch i:100, episode_len:23, loss :[3.1140473], entropy:2.7185049057006836\r\n", - "batch i:150, episode_len:14, loss :[3.0530043], entropy:2.62568998336792\r\n", - "batch i:200, episode_len:16, loss :[2.872697], entropy:2.4276740550994873\r\n", - "current self-play batch: 200\r\n", - "New best policy!!!!!!!!\r\n", - "batch i:250, episode_len:17, loss :[2.8419676], entropy:2.324467182159424\r\n", - "batch i:300, episode_len:12, loss :[2.8092303], entropy:2.369798183441162\r\n", - "batch i:350, episode_len:36, loss :[2.61333], entropy:2.25146222114563\r\n", - "batch i:400, episode_len:16, loss :[2.6112113], entropy:2.2541027069091797\r\n", - "current self-play batch: 400\r\n", - "New best policy!!!!!!!!\r\n", - "batch i:450, episode_len:19, loss :[2.668013], entropy:2.2586355209350586\r\n", - "batch i:500, episode_len:11, loss :[2.594069], entropy:2.1295700073242188\r\n" - ] - } - ], - "source": [ - "# 对于五子棋的AlphaZero的训练的实现\n", - "\n", - "class TrainPipeline():\n", - " def __init__(self, init_model=None, file_path='test'):\n", - " # 五子棋逻辑和棋盘UI的参数\n", - " self.board_width = 6 ###为了更快的验证算法,可以调整棋盘大小为(8x8) ,(6x6)\n", - " self.board_height = 6\n", - " self.n_in_row = 5\n", - " self.board = Board(width=self.board_width,\n", - " height=self.board_height,\n", - " n_in_row=self.n_in_row)\n", - " self.game = Game_UI(self.board)\n", - " # 训练参数\n", - " self.learn_rate = 2e-3\n", - " self.lr_multiplier = 1.0 # 基于KL自适应地调整学习率\n", - " self.temp = 1.0 # 临时变量\n", - " self.n_playout = 400 # 每次移动的模拟次数\n", - " self.c_puct = 5\n", - " self.buffer_size = 10000 #经验池大小 10000\n", - " self.batch_size = 512 # 训练的mini-batch大小 512\n", - " self.data_buffer = deque(maxlen=self.buffer_size)\n", - " self.play_batch_size = 1\n", - " self.epochs = 5 # 每次更新的train_steps数量\n", - " self.kl_targ = 0.02\n", - " self.check_freq = 200 #评估模型的频率,可以设置大一些比如500\n", - " self.game_batch_num = 500\n", - " self.best_win_ratio = 0.0\n", - " # 用于纯粹的mcts的模拟数量,用作评估训练策略的对手\n", - " self.pure_mcts_playout_num = 1000\n", - " if init_model:\n", - " # 从初始的策略价值网开始训练\n", - " self.policy_value_net = PolicyValueNet(self.board_width,\n", - " self.board_height,\n", - " model_file=init_model)\n", - " else:\n", - " # 从新的策略价值网络开始训练\n", - " self.policy_value_net = PolicyValueNet(self.board_width,\n", - " self.board_height)\n", - " # 定义训练机器人\n", - " self.mcts_player = MCTSPlayer(self.policy_value_net.policy_value_fn,\n", - " c_puct=self.c_puct,\n", - " n_playout=self.n_playout,\n", - " is_selfplay=1)\n", - " self.file_path = file_path # 存储训练参数文件位置\n", - " self.episode_len = 0\n", - "\n", - " def get_equi_data(self, play_data):\n", - " \"\"\"通过旋转和翻转来增加数据集\n", - " play_data: [(state, mcts_prob, winner_z), ..., ...]\n", - " \"\"\"\n", - " extend_data = []\n", - " for state, mcts_porb, winner in play_data:\n", - " for i in [1, 2, 3, 4]:\n", - " # 逆时针旋转\n", - " equi_state = np.array([np.rot90(s, i) for s in state])\n", - " equi_mcts_prob = np.rot90(np.flipud(\n", - " mcts_porb.reshape(self.board_height, self.board_width)), i)\n", - " extend_data.append((equi_state,\n", - " np.flipud(equi_mcts_prob).flatten(),\n", - " winner))\n", - " # 水平翻转\n", - " equi_state = np.array([np.fliplr(s) for s in equi_state])\n", - " equi_mcts_prob = np.fliplr(equi_mcts_prob)\n", - " extend_data.append((equi_state,\n", - " np.flipud(equi_mcts_prob).flatten(),\n", - " winner))\n", - " return extend_data\n", - "\n", - " def collect_selfplay_data(self, n_games=1):\n", - " \"\"\"收集自我博弈数据进行训练\"\"\"\n", - " for i in range(n_games):\n", - " winner, play_data = self.game.start_play_train(self.mcts_player, temp=self.temp)\n", - " play_data = list(play_data)\n", - " self.episode_len = len(play_data)\n", - " # 增加数据\n", - " play_data = self.get_equi_data(play_data)\n", - " self.data_buffer.extend(play_data)\n", - "\n", - " def update_policy_value_net(self):\n", - " \"\"\"更新策略价值网络\"\"\"\n", - " mini_batch = random.sample(self.data_buffer, self.batch_size)\n", - " state_batch = [data[0] for data in mini_batch]\n", - " \n", - " state_batch= np.array( state_batch).astype(\"float32\")\n", - " \n", - " mcts_probs_batch = [data[1] for data in mini_batch]\n", - " mcts_probs_batch= np.array( mcts_probs_batch).astype(\"float32\")\n", - " \n", - " winner_batch = [data[2] for data in mini_batch]\n", - " winner_batch= np.array( winner_batch).astype(\"float32\")\n", - " \n", - " old_probs, old_v = self.policy_value_net.policy_value_evaluate(state_batch)\n", - " loss = kl = 0\n", - " for i in range(self.epochs):\n", - " loss = self.policy_value_net.train_step(\n", - " state_batch,\n", - " mcts_probs_batch,\n", - " winner_batch,\n", - " self.learn_rate * self.lr_multiplier)\n", - " new_probs, new_v = self.policy_value_net.policy_value_evaluate(state_batch)\n", - " kl = np.mean(np.sum(old_probs * (\n", - " np.log(old_probs + 1e-10) - np.log(new_probs + 1e-10)),\n", - " axis=1)\n", - " )\n", - " if kl > self.kl_targ * 4: # early stopping if D_KL diverges badly\n", - " break\n", - " # 自适应调节学习率\n", - " if kl > self.kl_targ * 2 and self.lr_multiplier > 0.1:\n", - " self.lr_multiplier /= 1.5\n", - " elif kl < self.kl_targ / 2 and self.lr_multiplier < 10:\n", - " self.lr_multiplier *= 1.5\n", - " return loss\n", - "\n", - " def evaluate_policy_value_net(self, n_games=10):\n", - " \"\"\"\n", - " 通过与纯的MCTS算法对抗来评估训练的策略\n", - " 注意:这仅用于监控训练进度\n", - " \"\"\"\n", - " current_mcts_player = MCTSPlayer(self.policy_value_net.policy_value_fn,\n", - " c_puct=self.c_puct,\n", - " n_playout=self.n_playout)\n", - " pure_mcts_player = MCTSPlayer(c_puct=5,\n", - " n_playout=self.pure_mcts_playout_num,\n", - " mode='eval')\n", - " win_cnt = defaultdict(int)\n", - " for i in range(n_games):\n", - " winner = self.game.start_play_evaluate(current_mcts_player,\n", - " pure_mcts_player,\n", - " start_player=i % 2)\n", - " win_cnt[winner] += 1\n", - " win_ratio = 1.0 * (win_cnt[1] + 0.5 * win_cnt[-1]) / n_games\n", - " print(\"num_playouts:{}, win: {}, lose: {}, tie:{}\".format(\n", - " self.pure_mcts_playout_num,\n", - " win_cnt[1], win_cnt[2], win_cnt[-1]))\n", - " return win_ratio\n", - "\n", - " def run(self):\n", - " \"\"\"开始训练\"\"\"\n", - " root = os.getcwd()\n", - "\n", - " dst_path = os.path.join(root, self.file_path)\n", - "\n", - " if not os.path.exists(dst_path):\n", - " os.makedirs(dst_path)\n", - "\n", - " loss_list = []\n", - " try:\n", - " for i in range(self.game_batch_num):\n", - " self.collect_selfplay_data(self.play_batch_size)\n", - " if len(self.data_buffer) > self.batch_size:\n", - " loss = self.update_policy_value_net()\n", - " loss_list.append(loss)\n", - "\n", - " if (i + 1) % 50 == 0:\n", - " print(\"batch i:{}, episode_len:{}, loss :{},\".format(i + 1, self.episode_len, loss,))\n", - " self.policy_value_net.save_model(os.path.join(dst_path, 'current_policy_step.model'))\n", - " # 检查当前模型的性能,保存模型的参数\n", - " if (i + 1) % self.check_freq == 0:\n", - " print(\"current self-play batch: {}\".format(i + 1))\n", - " win_ratio = self.evaluate_policy_value_net()\n", - " self.policy_value_net.save_model(os.path.join(dst_path, 'current_policy.model'))\n", - " if win_ratio > self.best_win_ratio:\n", - " print(\"New best policy!!!!!!!!\")\n", - " self.best_win_ratio = win_ratio\n", - " # 更新最好的策略\n", - " self.policy_value_net.save_model(os.path.join(dst_path, 'best_policy.model'))\n", - " if (self.best_win_ratio == 1.0 and\n", - " self.pure_mcts_playout_num < 8000):\n", - " self.pure_mcts_playout_num += 1000\n", - " self.best_win_ratio = 0.0\n", - " except KeyboardInterrupt:\n", - " print('\\n\\rquit')\n", - " finally:\n", - " return loss_list\n", - "\n", - "\n", - "if __name__ == '__main__':\n", - " device = paddle.get_device() \n", - " paddle.set_device(device)\n", - " # model_path = 'model_ygh/best_policy.model'\n", - " # model_path = 'dist/current_policy.model'\n", - "\n", - " # training_pipeline = TrainPipeline(model_path)\n", - " training_pipeline = TrainPipeline(None)\n", - " loss_list = training_pipeline.run()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "## 6. 训练结果与展示:\n", - "\n", - "### 训练过程\n", - "![](https://ai-studio-static-online.cdn.bcebos.com/870e8e440d4744c69ca10a3c80db77684227c25ae1c14d2e9ae88b25ffea32be)\n", - "\n", - "\n", - "### 最终下棋的效果\n", - "![](https://ai-studio-static-online.cdn.bcebos.com/225bd09c070c4177814a408689cb45c0ac8ca042f4e74e81897fc41cb79bc833)\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "py35-paddle1.2.0" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.4" - } - }, - "nbformat": 4, - "nbformat_minor": 4 +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# 用飞桨框架2.0造一个会下五子棋的AI模型\n", + "\n", + "作者信息:yangguohao (https://github.com/yangguohao/)\n", + "\n", + "更新日期:2023 年 2 月 14 日\n", + "\n", + "## 1. 简要介绍\n", + "\n", + "### AlphaZero\n", + "\n", + "* [AlphaZero](https://arxiv.org/abs/1712.01815) 是由 Alphabet 旗下的子公司 DeepMind 研发的强化学习模型。 相较于之前的两代版本 [AlphaGo](https://www.nature.com/articles/nature16961) 和 [AlphaGoZero](https://www.nature.com/articles/nature24270),AlphaZero 完全无需人工特征、无需任何人类棋谱、甚至无需任何特定的策略和算法。\n", + "\n", + "* 作为一个通用模型,AlphaZero 不只针对围棋,而是同时学习了三种棋类-日本将棋、国际象棋以及围棋。从零开始,经过短时间的训练,AlphaZero 完胜各个领域里的最强AI。包括国际象棋的Stockfish、将棋的Elmo,以及围棋的前辈AlphaGo Zero。\n", + "\n", + "* 其核心主要为 蒙特卡洛树搜索(MCTS) 和 策略价值深度网络。通俗地说,蒙特卡洛树搜索能让 AlphaZero 多想几步棋,看的更远。而策略价值网络能让 AlphaZero 更准确地评估当前的棋局,提升蒙特卡洛树搜索的精度。两者相辅相成从而决定每一步的落子。\n", + "\n", + "![](https://ai-studio-static-online.cdn.bcebos.com/abb79e88765242ccb9df65f2cf4877fc83be041546bf47e293dcae1b98e7e8bb)\n", + "\n", + "\n", + "### 本项目简介\n", + "\n", + "* 本项目是AlphaZero算法的一个实现(使用PaddlePaddle框架),用于玩简单的棋盘游戏Gomoku(也称为五子棋),使用纯粹的自我博弈的方式开始训练。\n", + "\n", + "* Gomoku游戏比围棋或象棋简单得多,因此我们可以专注于AlphaZero的训练,在一台PC机上几个小时内就可以获得一个让你不可忽视的AI模型。\n", + "\n", + "![](https://ai-studio-static-online.cdn.bcebos.com/d510e461a8d84be3a1d0952874099910f4ac4da475e2424d862251d20f23c0f3)\n", + "\n", + "\n", + "* 因为和围棋相比,五子棋的规则较为简单,落子空间也比较小,因此没有用到AlphaGo Zero中大量使用的残差网络,只使用了卷积层和全连接层。本项目的网路简单,无需使用大量的计算就可以进行运行训练。使用的 Paddle 版本为 2.4.0。\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "## 2. 环境配置\n", + "本教程基于 PaddlePaddle 2.4.0 编写,如果你的环境不是本版本,请先参考官网安装 PaddlePaddle 2.4.0。" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "!pip install pygame" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "import random\n", + "import copy\n", + "import os\n", + "import time\n", + "from collections import defaultdict, deque\n", + "\n", + "import numpy as np\n", + "import paddle\n", + "import paddle.nn as nn \n", + "import paddle.nn.functional as F\n", + "import pygame\n", + "from pygame.locals import *\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "### 游戏环境\n", + "\n", + "初始化游戏环境,可以跳过该内容。\n", + "\n", + "主要是针对五子棋棋盘大小,走子以及游戏规则的设定,以及棋盘棋子等可视化的 UI 设定。" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "\n", + "class Board(object):\n", + " \"\"\"棋盘游戏逻辑控制\"\"\"\n", + "\n", + " def __init__(self, **kwargs):\n", + " self.width = int(kwargs.get('width', 15)) # 棋盘宽度\n", + " self.height = int(kwargs.get('height', 15)) # 棋盘高度\n", + " self.states = {} # 棋盘状态为一个字典,键: 移动步数,值: 玩家的棋子类型\n", + " self.n_in_row = int(kwargs.get('n_in_row', 5)) # 5个棋子一条线则获胜\n", + " self.players = [1, 2] # 玩家1,2\n", + "\n", + " def init_board(self, start_player=0):\n", + " # 初始化棋盘\n", + "\n", + " # 当前棋盘的宽高小于5时,抛出异常(因为是五子棋)\n", + " if self.width < self.n_in_row or self.height < self.n_in_row:\n", + " raise Exception('棋盘的长宽不能少于{}'.format(self.n_in_row))\n", + " self.current_player = self.players[start_player] # 先手玩家\n", + " self.availables = list(range(self.width * self.height)) # 初始化可用的位置列表\n", + " self.states = {} # 初始化棋盘状态\n", + " self.last_move = -1 # 初始化最后一次的移动位置\n", + "\n", + " def current_state(self):\n", + " \"\"\"\n", + " 从当前玩家的角度返回棋盘状态。\n", + " 状态形式: 4 * 宽 * 高\n", + " \"\"\"\n", + " # 使用4个15x15的二值特征平面来描述当前的局面\n", + " # 前两个平面分别表示当前player的棋子位置和对手player的棋子位置,有棋子的位置是1,没棋子的位置是0\n", + " # 第三个平面表示对手player最近一步的落子位置,也就是整个平面只有一个位置是1,其余全部是0\n", + " # 第四个平面表示的是当前player是不是先手player,如果是先手player则整个平面全部为1,否则全部为0\n", + " square_state = np.zeros((4, self.width, self.height))\n", + " if self.states:\n", + " moves, players = np.array(list(zip(*self.states.items())))\n", + " move_curr = moves[players == self.current_player] # 获取棋盘状态上属于当前玩家的所有移动值\n", + " move_oppo = moves[players != self.current_player] # 获取棋盘状态上属于对方玩家的所有移动值\n", + " square_state[0][move_curr // self.width, # 对第一个特征平面填充值(当前玩家)\n", + " move_curr % self.height] = 1.0\n", + " square_state[1][move_oppo // self.width, # 对第二个特征平面填充值(对方玩家)\n", + " move_oppo % self.height] = 1.0\n", + " # 指出最后一个移动位置\n", + " square_state[2][self.last_move // self.width, # 对第三个特征平面填充值(对手最近一次的落子位置)\n", + " self.last_move % self.height] = 1.0\n", + " if len(self.states) % 2 == 0: # 对第四个特征平面填充值,当前玩家是先手,则填充全1,否则为全0\n", + " square_state[3][:, :] = 1.0\n", + " # 将每个平面棋盘状态按行逆序转换(第一行换到最后一行,第二行换到倒数第二行..)\n", + " return square_state[:, ::-1, :]\n", + "\n", + " def do_move(self, move):\n", + " # 根据移动的数据更新各参数\n", + " self.states[move] = self.current_player # 将当前的参数存入棋盘状态中\n", + " self.availables.remove(move) # 从可用的棋盘列表移除当前移动的位置\n", + " self.current_player = (\n", + " self.players[0] if self.current_player == self.players[1]\n", + " else self.players[1]\n", + " ) # 改变当前玩家\n", + " self.last_move = move # 记录最后一次的移动位置\n", + "\n", + " def has_a_winner(self):\n", + " # 是否产生赢家\n", + " width = self.width # 棋盘宽度\n", + " height = self.height # 棋盘高度\n", + " states = self.states # 状态\n", + " n = self.n_in_row # 获胜需要的棋子数量\n", + "\n", + " # 当前棋盘上所有的落子位置\n", + " moved = list(set(range(width * height)) - set(self.availables))\n", + " if len(moved) < self.n_in_row + 2:\n", + " # 当前棋盘落子数在7个以上时会产生赢家,落子数低于7个时,直接返回没有赢家\n", + " return False, -1\n", + "\n", + " # 遍历落子数\n", + " for m in moved:\n", + " h = m // width\n", + " w = m % width # 获得棋子的坐标\n", + " player = states[m] # 根据移动的点确认玩家\n", + "\n", + " # 判断各种赢棋的情况\n", + " # 横向5个\n", + " if (w in range(width - n + 1) and\n", + " len(set(states.get(i, -1) for i in range(m, m + n))) == 1):\n", + " return True, player\n", + "\n", + " # 纵向5个\n", + " if (h in range(height - n + 1) and\n", + " len(set(states.get(i, -1) for i in range(m, m + n * width, width))) == 1):\n", + " return True, player\n", + "\n", + " # 左上到右下斜向5个\n", + " if (w in range(width - n + 1) and h in range(height - n + 1) and\n", + " len(set(states.get(i, -1) for i in range(m, m + n * (width + 1), width + 1))) == 1):\n", + " return True, player\n", + "\n", + " # 右上到左下斜向5个\n", + " if (w in range(n - 1, width) and h in range(height - n + 1) and\n", + " len(set(states.get(i, -1) for i in range(m, m + n * (width - 1), width - 1))) == 1):\n", + " return True, player\n", + "\n", + " # 当前都没有赢家,返回False\n", + " return False, -1\n", + "\n", + " def game_end(self):\n", + " \"\"\"检查当前棋局是否结束\"\"\"\n", + " win, winner = self.has_a_winner()\n", + " if win:\n", + " return True, winner\n", + " elif not len(self.availables):\n", + " # 棋局布满,没有赢家\n", + " return True, -1\n", + " return False, -1\n", + "\n", + " def get_current_player(self):\n", + " return self.current_player\n", + "\n", + "\n", + "# 加上UI的布局的训练方式\n", + "class Game_UI(object):\n", + " \"\"\"游戏控制区域\"\"\"\n", + "\n", + " def __init__(self, board, **kwargs):\n", + " self.board = board # 加载棋盘控制类\n", + "\n", + " # 初始化 pygame\n", + " pygame.init()\n", + "\n", + " def start_play_evaluate(self, player1, player2, start_player=0):\n", + " \"\"\"开始一局游戏,评估当前的价值策略网络的胜率\"\"\"\n", + " if start_player not in (0, 1):\n", + " # 如果玩家不在玩家1,玩家2之间,抛出异常\n", + " raise Exception('开始的玩家必须为0(玩家1)或1(玩家2)')\n", + " self.board.init_board(start_player) # 初始化棋盘\n", + " p1, p2 = self.board.players # 加载玩家1,玩家2\n", + " player1.set_player_ind(p1) # 设置玩家1\n", + " player2.set_player_ind(p2) # 设置玩家2\n", + " players = {p1: player1, p2: player2}\n", + " \n", + " while True:\n", + "\n", + " current_player = self.board.current_player # 获取当前玩家\n", + " player_in_turn = players[current_player] # 当前玩家的信息\n", + " move = player_in_turn.get_action(self.board) # 基于MCTS的AI下一步落子\n", + " self.board.do_move(move) # 根据下一步落子的状态更新棋盘各参数\n", + " \n", + " # 判断当前棋局是否结束\n", + " end, winner = self.board.game_end()\n", + " # 结束\n", + " if end:\n", + " win = winner\n", + " break\n", + " \n", + " return win\n", + "\n", + " def start_play_train(self, player, temp=1e-3):\n", + " \"\"\" \n", + " 开始自我博弈,使用MCTS玩家开始自己玩游戏,重新使用搜索树并存储自己玩游戏的数据\n", + " (state, mcts_probs, z) 提供训练\n", + " \"\"\"\n", + " self.board.init_board() # 初始化棋盘\n", + " states, mcts_probs, current_players = [], [], [] # 状态,mcts的行为概率,当前玩家\n", + "\n", + " while True:\n", + "\n", + " # 根据当前棋盘状态返回可能得行为,及行为对应的概率\n", + " move, move_probs = player.get_action(self.board,\n", + " temp=temp,\n", + " return_prob=1)\n", + " # 存储数据\n", + " states.append(self.board.current_state()) # 存储状态数据\n", + " mcts_probs.append(move_probs) # 存储行为概率数据\n", + " current_players.append(self.board.current_player) # 存储当前玩家\n", + " # 执行一个移动\n", + " self.board.do_move(move)\n", + "\n", + " # 判断该局游戏是否终止\n", + " end, winner = self.board.game_end()\n", + " if end:\n", + " # 从每个状态的当时的玩家的角度看待赢家\n", + " winners_z = np.zeros(len(current_players))\n", + " if winner != -1:\n", + " # 没有赢家时\n", + " winners_z[np.array(current_players) == winner] = 1.0\n", + " winners_z[np.array(current_players) != winner] = -1.0\n", + " # 重置MSCT的根节点\n", + " player.reset_player()\n", + " return winner, zip(states, mcts_probs, winners_z)\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "## 3. 价值策略网络\n", + "\n", + "* 原论文中的策略价值网络的结构是一个 CNN 组成的神经网络,初始游戏状态的张量在经过一个基本的卷积后,使用了19层或者39层的深度残差网络,最后输出价值和策略两个部分。\n", + "\n", + "![](https://ai-studio-static-online.cdn.bcebos.com/ac1bbf4b83b04a1d8f7e0abee8ae51fc91725b3526ac440da643a2f8d82b28f9)\n", + "\n", + "* 而这里为了演示算法,网络并不复杂,深度较浅。但是整体的逻辑与 AlphaZero 相似,由公共网络层、行动策略网络层和状态价值网络层构成。公共网络层使用卷积网络对棋盘上的状态进行特征提取,而行动策略层用以输出每个可落子点的落子概率,状态价值层用以输出可落子点的价值的评分。" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "class Net(paddle.nn.Layer):\n", + " def __init__(self,board_width, board_height):\n", + " super(Net, self).__init__()\n", + " self.board_width = board_width\n", + " self.board_height = board_height\n", + " # 公共网络层\n", + " self.conv1 = nn.Conv2D(in_channels=4,out_channels=32,kernel_size=3,padding=1)\n", + " self.conv2 = nn.Conv2D(in_channels=32,out_channels=64,kernel_size=3,padding=1)\n", + " self.conv3 = nn.Conv2D(in_channels=64,out_channels=128,kernel_size=3,padding=1)\n", + " # 行动策略网络层\n", + " self.act_conv1 = nn.Conv2D(in_channels=128,out_channels=4,kernel_size=1,padding=0)\n", + " self.act_fc1 = nn.Linear(4*self.board_width*self.board_height,\n", + " self.board_width*self.board_height)\n", + " self.val_conv1 = nn.Conv2D(in_channels=128,out_channels=2,kernel_size=1,padding=0)\n", + " self.val_fc1 = nn.Linear(2*self.board_width*self.board_height, 64)\n", + " self.val_fc2 = nn.Linear(64, 1)\n", + "\n", + " def forward(self, inputs):\n", + " # 公共网络层 \n", + " x = F.relu(self.conv1(inputs))\n", + " x = F.relu(self.conv2(x))\n", + " x = F.relu(self.conv3(x))\n", + " # 行动策略网络层\n", + " x_act = F.relu(self.act_conv1(x))\n", + " x_act = paddle.reshape(\n", + " x_act, [-1, 4 * self.board_height * self.board_width])\n", + " \n", + " x_act = F.log_softmax(self.act_fc1(x_act)) \n", + " # 状态价值网络层\n", + " x_val = F.relu(self.val_conv1(x))\n", + " x_val = paddle.reshape(\n", + " x_val, [-1, 2 * self.board_height * self.board_width])\n", + " x_val = F.relu(self.val_fc1(x_val))\n", + " x_val = F.tanh(self.val_fc2(x_val))\n", + "\n", + " return x_act,x_val" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "* 在定义好策略和价值网络的基础上,接下来实现PolicyValueNet类,该类主要定义:policy_value_fn()方法,主要用于蒙特卡洛树搜索时评估叶子节点对应局面评分、该局所有可行动作及对应概率;另一个方法train_step(),主要用于更新自我对弈收集数据上策略价值网络的参数。\n", + "\n", + "* 在训练神经网络阶段,我们使用自我对战学习阶段得到的样本集合 (s,π,z) 来训练我们神经网络的模型参数。训练的目的是对于每个输入的棋盘状态 s, 神经网络输出的概率 p 和价值 v 和我们训练样本中的 π,z 差距尽可能的少。\n", + "\n", + "* 损失函数由三部分组成,\n", + " * 第一部分是均方误差损失函数,对应代码中 value_loss,用于评估神经网络预测的胜负结果和真实结果之间的差异。\n", + " * 第二部分是交叉熵损失函数,对应 policy_loss,用于评估神经网络的输出策略和我们 MCTS 输出的策略的差异。\n", + " * 第三部分是L2正则化项,对应优化器 self.optimizer 中的 weight_decay, 用于控制网络模型的复杂度。" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "\n", + "class PolicyValueNet():\n", + " \"\"\"策略&值网络 \"\"\"\n", + " def __init__(self, board_width, board_height,\n", + " model_file=None, use_gpu=True):\n", + " self.use_gpu = use_gpu\n", + " self.board_width = board_width\n", + " self.board_height = board_height\n", + " self.l2_const = 1e-3 # coef of l2 penalty\n", + " \n", + "\n", + " self.policy_value_net = Net(self.board_width, self.board_height) \n", + " \n", + " self.optimizer = paddle.optimizer.Adam(learning_rate=0.02,\n", + " parameters=self.policy_value_net.parameters(), weight_decay=self.l2_const)\n", + " \n", + "\n", + " if model_file:\n", + " net_params = paddle.load(model_file)\n", + " self.policy_value_net.set_state_dict(net_params)\n", + " \n", + " def policy_value_evaluate(self, state_batch):\n", + " \"\"\"\n", + " 评估函数\n", + " Args:\n", + " input: 一组棋盘状态\n", + " output: 根据棋盘状态输出对应的动作概率及价值\n", + " \"\"\"\n", + " state_batch = paddle.to_tensor(state_batch)\n", + " log_act_probs, value = self.policy_value_net(state_batch)\n", + " act_probs = np.exp(log_act_probs.numpy())\n", + " return act_probs, value.numpy()\n", + "\n", + " def policy_value_fn(self, board):\n", + " \"\"\"\n", + " 评估场面局势,给出每个位置的概率及价值\n", + " Args:\n", + " input: 棋盘状态\n", + " output: 返回一组列表,包含棋盘每个可下的点的动作概率以及价值得分。\n", + " \"\"\"\n", + " legal_positions = board.availables\n", + " current_state = np.ascontiguousarray(board.current_state().reshape(\n", + " -1, 4, self.board_width, self.board_height)).astype(\"float32\")\n", + "\n", + " act_probs, value = self.policy_value_evaluate(current_state)\n", + " act_probs = zip(legal_positions, act_probs.flatten()[legal_positions])\n", + " return act_probs, value\n", + "\n", + " def train_step(self, state_batch, mcts_probs, winner_batch, lr=0.002):\n", + " \"\"\"用采样得到的样本集合对策略价值网络进行一次训练\"\"\"\n", + " # wrap in Tensor\n", + " state_batch = paddle.to_tensor(state_batch)\n", + " mcts_probs = paddle.to_tensor(mcts_probs)\n", + " winner_batch = paddle.to_tensor(winner_batch)\n", + "\n", + " # zero the parameter gradients\n", + " self.optimizer.clear_gradients()\n", + " # set learning rate\n", + " self.optimizer.set_lr(lr)\n", + "\n", + " # forward\n", + " log_act_probs, value = self.policy_value_net(state_batch)\n", + " # define the loss = (z - v)^2 - pi^T * log(p) + c||theta||^2\n", + " # Note: the L2 penalty is incorporated in optimizer\n", + " value = paddle.reshape(x=value, shape=[-1])\n", + " value_loss = F.mse_loss(input=value, label=winner_batch)\n", + " policy_loss = -paddle.mean(paddle.sum(mcts_probs*log_act_probs, axis=1))\n", + " loss = value_loss + policy_loss\n", + " # backward and optimize\n", + " loss.backward()\n", + " self.optimizer.minimize(loss)\n", + " return loss.numpy()\n", + "\n", + " def get_policy_param(self):\n", + " net_params = self.policy_value_net.state_dict()\n", + " return net_params\n", + "\n", + " def save_model(self, model_file):\n", + " \"\"\"保存模型\"\"\"\n", + " net_params = self.get_policy_param() # get model params\n", + " paddle.save(net_params, model_file)" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "## 4. 蒙特卡洛树搜索(MCTS)\n", + "\n", + "传统的AI博弈树搜索算法效率都很低,因为这些算法在做出最终选择前需要穷尽每一种走法。即便很少的分支因子的游戏,其每一步的搜索空间也会爆炸式增长。分支因子就是所有可能的走法的数量,这个数量会随着游戏的进行不断变化。因此,你可以试着计算一个游戏的平均分支因子数,国际象棋的平均分支因子是35,而围棋则是250。这意味着,在国际象棋中,仅走两步就有1,225(35²)种可能的棋面,而在围棋中,这个数字会变成62,500(250²)。因此,上述的价值策略神经网络将指导并告诉我们哪些博弈路径值得探索,从而避免被许多无用的搜索路径所淹没。再结合蒙特卡洛树选择最佳的走法。\n", + "\n", + "### 棋类游戏的蒙特卡洛树搜索(MCTS)\n", + "使用MCTS的具体做法是这样的,给定一个棋面,MCTS共进行N次模拟。主要的搜索阶段有4个:选择,扩展,仿真和回溯\n", + "\n", + "![](https://ai-studio-static-online.cdn.bcebos.com/73384055df364b44a49e7e206a9015790be7b3c0aa1942d0a4e57aa617fad087)\n", + "\n", + "* 第一步是选择(Selection),这一步会从根节点开始,每次都选一个“最值得搜索的子节点”,一般使用上限置信区间算法 (Upper Confidence Bound Apply to Tree, UCT) 选择分数最高的节点,直到来到一个“存在未扩展的子节点”的节点\n", + "\n", + "* 第二步是扩展(Expansion),在这个搜索到的“存在未扩展的子节点”之上,加上一个没有历史记录的子节点并初始化该子节点\n", + "\n", + "* 第三步是仿真(simulation),从上面这个没有试过的着法开始,用一个简单策略比如快速走子策略 (Rollout policy) 走到底,得到一个胜负结果。快速走子策略虽然不是很精确,但是速度较快,在这里具有优势。因为如果这个策略走得慢,结果虽然会更准确,但由于耗时多了,在单位时间内的模拟次数就少了,所以不一定会棋力更强,有可能会更弱。这也是为什么我们一般只模拟一次,因为如果模拟多次,虽然更准确,但更慢。\n", + "\n", + "* 第四步是回溯 (backpropagation), 将我们最后得到的胜负结果回溯加到MCTS树结构上。注意除了之前的MCTS树要回溯外,新加入的节点也要加上一次胜负历史记录。\n", + "\n", + "以上就是MCTS搜索的整个过程。这4步一般是通用的,但是MCTS树结构上保存的内容而一般根据要解决的问题和建模的复杂度而不同。\n", + "### 基于神经网络的蒙特卡洛树搜索(MCTS)\n", + "N(s,a) :记录边的访问次数;\n", + "W(s,a): 合计行动价值;\n", + "Q(s,a) :平均行动价值;\n", + "P(s,a) :选择该条边的先验概率;\n", + "* 首先是选择(Selection):在MCTS内部,出现过的局面,我们会使用UCT选择子分支。最终我们会选择Q+U最大的子分支作为搜索分支,一直走到棋局结束,或者走到了没有到终局MCTS的叶子节点。$c_{puct}$是决定探索程度的一个系数\n", + "\n", + "![](https://ai-studio-static-online.cdn.bcebos.com/a3edc34d8d554068becbfb21b7f6a5f7fc0b43f804eb45ed9c92a27d38478fdd)\n", + "\n", + "* 然后是扩展(Expansion)和仿真(simulation):对于叶子节点状态s,会利用神经网络对叶子节点做预测,得到当前叶子节点的各个可能的子节点位置sL落子的概率p和对应的价值v,对于这些可能的新节点我们在MCTS中创建出来,初始化其分支上保存的信息为\n", + "\n", + "![](https://ai-studio-static-online.cdn.bcebos.com/aed60f9babbb4c208f19d480fd25558b903c4836a2e5438bb858e6ddcaa218c9)\n", + "\n", + "\n", + "* 最后是回溯(backpropagation):将新叶子节点分支的信息回溯累加到祖先节点分支上去。这个回溯的逻辑也是很简单的,从每个叶子节点L依次向根节点回溯,并依次更新上层分支数据结构如下:\n", + "\n", + "![](https://ai-studio-static-online.cdn.bcebos.com/b39e2bd4ab0f42a691e6a239443bc57f0dfac929a03b428b8a06b0416d4340c3)\n", + "\n", + "MCTS搜索完毕后,模型就可以在MCTS的根节点s基于以下公式选择行棋的MCTS分支了:\n", + "\n", + "![](https://ai-studio-static-online.cdn.bcebos.com/3a14cd6be857468b9bcbcdee61d6ecdb325a864649284df4a8aa5b1d2b7605a0)\n", + "\n", + "τ是用来控制探索的程度,τ的取值介于(0,1]之间,当τ越接近于1时,神经网络的采样越接近于MCTS的原始采样,当τ越接近于0时,神经网络的采样越接近于贪婪策略,即选择最大访问次数N所对应的动作。\n", + "因为在τ很小的情况下,直接计算访问次数N的τ次方根可能会导致数值异常,为了避免这种情况,在计算行动概率时,先将访问次数N加上一个非常小的数值(本项目是1e-10),取自然对数后乘上1/τ,再用一个简化的softmax函数将输出还原为概率,这和原始公式在数学上基本上是等效的。\n", + "\n", + "**关键点是什么?**\n", + "* 通过每一次模拟,MCTS依靠神经网络, 使用累计价值 (Q)、神经网络给出的走法先验概率 (P) 以及访问对应节点的频率这些数字的组合,沿着最有希望获胜的路径(换句话说,也就是具有最高置信区间上界的路径)进行探索。\n", + "\n", + "* 在每一次模拟中,MCTS会尽可能向纵深进行探索直至遇到它从未见过的盘面状态,在这种情况下,它会通过神经网络来评估该盘面状态的优劣。\n", + "\n", + "* 巧妙了使用MCTS搜索树和神经网络一起,通过MCTS搜索树优化神经网络参数,反过来又通过优化的神经网络指导MCTS搜索。\n", + "\n", + "\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ + "\n", + "def softmax(x):\n", + " probs = np.exp(x - np.max(x))\n", + " probs /= np.sum(probs)\n", + " return probs\n", + "\n", + "\n", + "def policy_value_fn(board):\n", + " \"\"\"\n", + " 接受状态并输出(动作,概率)列表的函数元组和状态的分数\"\"\"\n", + " # 返回统一概率和0分的纯MCTS\n", + " action_probs = np.ones(len(board.availables)) / len(board.availables)\n", + " return zip(board.availables, action_probs), 0\n", + "\n", + "\n", + "class TreeNode(object):\n", + " \"\"\"MCTS树中的节点。\n", + "\n", + " 每个节点跟踪其自身的值Q,先验概率P及其访问次数调整的先前得分u。\n", + " \"\"\"\n", + "\n", + " def __init__(self, parent, prior_p):\n", + " self._parent = parent\n", + " self._children = {} # 从动作到TreeNode的映射\n", + " self._n_visits = 0\n", + " self._Q = 0\n", + " self._u = 0\n", + " self._P = prior_p\n", + "\n", + " def expand(self, action_priors):\n", + " \"\"\"通过创建新子项来展开树。\n", + " action_priors:一系列动作元组及其先验概率根据策略函数.\n", + " \"\"\"\n", + " for action, prob in action_priors:\n", + " if action not in self._children:\n", + " self._children[action] = TreeNode(self, prob)\n", + "\n", + " def select(self, c_puct):\n", + " \"\"\"在子节点中选择能够提供最大行动价值Q的行动加上奖金u(P)。\n", + " return:(action,next_node)的元组\n", + " \"\"\"\n", + " return max(self._children.items(),\n", + " key=lambda act_node: act_node[1].get_value(c_puct))\n", + "\n", + " def update(self, leaf_value):\n", + " \"\"\"从叶节点评估中更新节点值\n", + " leaf_value: 这个子树的评估值来自从当前玩家的视角\n", + " \"\"\"\n", + " # 统计访问次数\n", + " self._n_visits += 1\n", + " # 更新Q值,取对于所有访问次数的平均数\n", + " self._Q += 1.0 * (leaf_value - self._Q) / self._n_visits\n", + "\n", + " def update_recursive(self, leaf_value):\n", + " \"\"\"就像调用update()一样,但是对所有祖先进行递归应用。\n", + " \"\"\"\n", + " # 如果它不是根节点,则应首先更新此节点的父节点。\n", + " if self._parent:\n", + " self._parent.update_recursive(-leaf_value)\n", + " self.update(leaf_value)\n", + "\n", + " def get_value(self, c_puct):\n", + " \"\"\"计算并返回此节点的值。它是叶评估Q和此节点的先验的组合\n", + " 调整了访问次数,u。\n", + " c_puct:控制相对影响的(0,inf)中的数字,该节点得分的值Q和先验概率P.\n", + " \"\"\"\n", + " self._u = (c_puct * self._P *\n", + " np.sqrt(self._parent._n_visits) / (1 + self._n_visits))\n", + " return self._Q + self._u\n", + "\n", + " def is_leaf(self):\n", + " \"\"\"检查叶节点(即没有扩展的节点)。\"\"\"\n", + " return self._children == {}\n", + "\n", + " def is_root(self):\n", + " return self._parent is None\n", + "\n", + "\n", + "class MCTS(object):\n", + " \"\"\"对蒙特卡罗树搜索的一个简单实现\"\"\"\n", + "\n", + " def __init__(self, policy_value_fn, c_puct=5, n_playout=10000, mode='train'):\n", + " \"\"\"\n", + " policy_value_fn:一个接收板状态和输出的函数(动作,概率)元组列表以及[-1,1]中的分数\n", + " (即来自当前的最终比赛得分的预期值玩家的观点)对于当前的玩家。\n", + " c_puct:(0,inf)中的数字,用于控制探索的速度收敛于最大值政策。 更高的价值意味着\n", + " 依靠先前的更多。\n", + " \"\"\"\n", + " self._root = TreeNode(None, 1.0)\n", + " self._policy = policy_value_fn\n", + " self._c_puct = c_puct\n", + " self._n_playout = n_playout\n", + " self.mode = mode\n", + "\n", + " def _playout(self, state):\n", + " \"\"\"从根到叶子运行单个播出,获取值\n", + " 叶子并通过它的父母传播回来。\n", + " State已就地修改,因此必须提供副本。\n", + " \"\"\"\n", + " node = self._root\n", + " while True:\n", + " if node.is_leaf():\n", + " break\n", + " # 贪心算法选择下一步行动\n", + " action, node = node.select(self._c_puct)\n", + " state.do_move(action)\n", + "\n", + " # 使用网络评估叶子,该网络输出(动作,概率)元组p的列表以及当前玩家的[-1,1]中的分数v。\n", + " action_probs, leaf_value = self._policy(state)\n", + " # 查看游戏是否结束\n", + " end, winner = state.game_end()\n", + " if not end:\n", + " node.expand(action_probs)\n", + " if self.mode == 'train':\n", + " if end:\n", + " # 对于结束状态,将叶子节点的值换成\"true\"\n", + " if winner == -1: # tie\n", + " leaf_value = 0.0\n", + " else:\n", + " leaf_value = (\n", + " 1.0 if winner == state.get_current_player() else -1.0\n", + " )\n", + " else:\n", + " # 通过随机的rollout评估叶子结点\n", + " leaf_value = self._evaluate_rollout(state)\n", + " # 在本次遍历中更新节点的值和访问次数\n", + " node.update_recursive(-leaf_value)\n", + "\n", + " @staticmethod\n", + " def _evaluate_rollout(state, limit=1000):\n", + " \"\"\"使用推出策略直到游戏结束,\n", + " 如果当前玩家获胜则返回+1,如果对手获胜则返回-1,\n", + " 如果是平局则为0。\n", + " \"\"\"\n", + " player = state.get_current_player()\n", + " winner = -1\n", + " for i in range(limit):\n", + " end, winner = state.game_end()\n", + " if end:\n", + " break\n", + " max_action = np.random.choice(board.availables)\n", + " state.do_move(max_action)\n", + " else:\n", + " # 如果没有从循环中断,请发出警告。\n", + " print(\"WARNING: rollout reached move limit\")\n", + " if winner == -1: # tie\n", + " return 0\n", + " else:\n", + " return 1 if winner == player else -1\n", + "\n", + " def get_move(self, state, temp=1e-3):\n", + " \"\"\"\n", + " 如果 prob 为 True,则按顺序运行所有播出并返回可用的操作及其相应的概率。\n", + " 否则按顺序运行所有播出并返回访问量最大的操作。\n", + " \"\"\"\n", + " for n in range(self._n_playout):\n", + " state_copy = copy.deepcopy(state)\n", + " self._playout(state_copy)\n", + " if self.mode == 'train':\n", + " # 根据根节点处的访问计数来计算移动概率\n", + " act_visits = [(act, node._n_visits)\n", + " for act, node in self._root._children.items()]\n", + " acts, visits = zip(*act_visits)\n", + " act_probs = softmax(1.0 / temp * np.log(np.array(visits) + 1e-10))\n", + "\n", + " return acts, act_probs\n", + "\n", + " return max(self._root._children.items(),\n", + " key=lambda act_node: act_node[1]._n_visits)[0]\n", + "\n", + " def update_with_move(self, last_move):\n", + " \"\"\"保留我们已经知道的关于子树的信息\n", + " \"\"\"\n", + " if last_move in self._root._children:\n", + " self._root = self._root._children[last_move]\n", + " self._root._parent = None\n", + " else:\n", + " self._root = TreeNode(None, 1.0)\n", + "\n", + " def __str__(self):\n", + " return \"MCTS\"\n", + "\n", + "\n", + "class MCTSPlayer(object):\n", + " \"\"\"基于MCTS的AI玩家\"\"\"\n", + "\n", + " def __init__(self, policy_value_function=policy_value_fn,\n", + " c_puct=5, n_playout=2000, is_selfplay=0, mode='train'):\n", + " self.mcts = MCTS(policy_value_function, c_puct, n_playout, mode)\n", + " self._is_selfplay = is_selfplay\n", + "\n", + " def set_player_ind(self, p):\n", + " self.player = p\n", + "\n", + " def reset_player(self):\n", + " self.mcts.update_with_move(-1)\n", + "\n", + " def get_action(self, board, temp=1e-3, return_prob=0):\n", + "\n", + " sensible_moves = board.availables\n", + " # 像alphaGo Zero论文一样使用MCTS算法返回的pi向量\n", + " move_probs = np.zeros(board.width * board.height)\n", + " if len(sensible_moves) > 0:\n", + " if self.mcts.mode == 'train':\n", + " acts, probs = self.mcts.get_move(board, temp)\n", + " move_probs[list(acts)] = probs\n", + " if self._is_selfplay:\n", + " # 添加Dirichlet Noise进行探索(自我训练所需)\n", + " move = np.random.choice(\n", + " acts,\n", + " p=0.75 * probs + 0.25 * np.random.dirichlet(0.3 * np.ones(len(probs)))\n", + " )\n", + " # 更新根节点并重用搜索树\n", + " self.mcts.update_with_move(move)\n", + " else:\n", + " # 使用默认的temp = 1e-3,它几乎相当于选择具有最高概率的移动\n", + " move = np.random.choice(acts, p=probs)\n", + " # 重置根节点\n", + " self.mcts.update_with_move(-1)\n", + "\n", + " if return_prob:\n", + " return move, move_probs\n", + " else:\n", + " return move\n", + " else:\n", + " move = self.mcts.get_move(board)\n", + " self.mcts.update_with_move(-1)\n", + " return move\n", + " else:\n", + " print(\"棋盘已满\")\n", + "\n", + " def __str__(self):\n", + " return \"MCTS {}\".format(self.player)\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "## 5. 模型训练\n", + "\n", + "* AlphaZero的算法流程,概括来说就是通过自我对弈收集数据,并用于更新策略价值网络,更新后的策略价值网络又会被用于后续的自我对弈过程中,从而产生高质量的自我对弈数据,这样相互促进、不断迭代,实现稳定的学习和提升。\n", + "\n", + "* 为了加快训练设置棋盘大小为 6×6,自我对弈500轮。\n", + "\n", + "### 模型训练过程以及伪代码\n", + "\n", + "每轮训练过程中,首先使用上文中提到的蒙特卡洛树搜索及策略价值网络进行一场自我对弈。\n", + "\n", + "在一场自我对弈中,每一手棋都通过蒙特卡洛树的四个步骤搜索走法,并得到最终落子的策略概率 Π。 在自我对弈最终结束时,标记胜负,并将其中每一手棋的状态概率以及胜负放入经验池中。\n", + "\n", + "最后通过对经验池采样,训练更新策略价值网络。\n", + "\n", + "下面给出图示及论文中的伪代码\n", + "\n", + "#### 图示\n", + "![](https://ai-studio-static-online.cdn.bcebos.com/0841d0771e704488bdbb989e1bdabbe2f82610f471a84a83b48826c35c5da23f)\n", + "\n", + "\n", + "#### 伪代码\n", + "![](https://ai-studio-static-online.cdn.bcebos.com/f306ff233cd746208896826bc0ca2f3e2d442b7187fd4fb5815f6b5e688f74ff)\n" + ], + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%% md\n" + } + } + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "execution": { + "iopub.execute_input": "2022-12-30T01:20:46.858489Z", + "iopub.status.busy": "2022-12-30T01:20:46.857980Z", + "iopub.status.idle": "2022-12-30T04:19:33.342924Z", + "shell.execute_reply": "2022-12-30T04:19:33.341753Z", + "shell.execute_reply.started": "2022-12-30T01:20:46.858457Z" + }, + "scrolled": true, + "tags": [], + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "batch i:50, episode_len:18, loss :[3.2428253], entropy:2.993258476257324\r\n", + "batch i:100, episode_len:23, loss :[3.1140473], entropy:2.7185049057006836\r\n", + "batch i:150, episode_len:14, loss :[3.0530043], entropy:2.62568998336792\r\n", + "batch i:200, episode_len:16, loss :[2.872697], entropy:2.4276740550994873\r\n", + "current self-play batch: 200\r\n", + "New best policy!!!!!!!!\r\n", + "batch i:250, episode_len:17, loss :[2.8419676], entropy:2.324467182159424\r\n", + "batch i:300, episode_len:12, loss :[2.8092303], entropy:2.369798183441162\r\n", + "batch i:350, episode_len:36, loss :[2.61333], entropy:2.25146222114563\r\n", + "batch i:400, episode_len:16, loss :[2.6112113], entropy:2.2541027069091797\r\n", + "current self-play batch: 400\r\n", + "New best policy!!!!!!!!\r\n", + "batch i:450, episode_len:19, loss :[2.668013], entropy:2.2586355209350586\r\n", + "batch i:500, episode_len:11, loss :[2.594069], entropy:2.1295700073242188\r\n" + ] + } + ], + "source": [ + "# 对于五子棋的AlphaZero的训练的实现\n", + "\n", + "class TrainPipeline():\n", + " def __init__(self, init_model=None, file_path='test'):\n", + " # 五子棋逻辑和棋盘UI的参数\n", + " self.board_width = 6 ###为了更快的验证算法,可以调整棋盘大小为(8x8) ,(6x6)\n", + " self.board_height = 6\n", + " self.n_in_row = 5\n", + " self.board = Board(width=self.board_width,\n", + " height=self.board_height,\n", + " n_in_row=self.n_in_row)\n", + " self.game = Game_UI(self.board)\n", + " # 训练参数\n", + " self.learn_rate = 2e-3\n", + " self.lr_multiplier = 1.0 # 基于KL自适应地调整学习率\n", + " self.temp = 1.0 # 临时变量\n", + " self.n_playout = 400 # 每次移动的模拟次数\n", + " self.c_puct = 5\n", + " self.buffer_size = 10000 #经验池大小 10000\n", + " self.batch_size = 512 # 训练的mini-batch大小 512\n", + " self.data_buffer = deque(maxlen=self.buffer_size)\n", + " self.play_batch_size = 1\n", + " self.epochs = 5 # 每次更新的train_steps数量\n", + " self.kl_targ = 0.02\n", + " self.check_freq = 200 #评估模型的频率,可以设置大一些比如500\n", + " self.game_batch_num = 500\n", + " self.best_win_ratio = 0.0\n", + " # 用于纯粹的mcts的模拟数量,用作评估训练策略的对手\n", + " self.pure_mcts_playout_num = 1000\n", + " if init_model:\n", + " # 从初始的策略价值网开始训练\n", + " self.policy_value_net = PolicyValueNet(self.board_width,\n", + " self.board_height,\n", + " model_file=init_model)\n", + " else:\n", + " # 从新的策略价值网络开始训练\n", + " self.policy_value_net = PolicyValueNet(self.board_width,\n", + " self.board_height)\n", + " # 定义训练机器人\n", + " self.mcts_player = MCTSPlayer(self.policy_value_net.policy_value_fn,\n", + " c_puct=self.c_puct,\n", + " n_playout=self.n_playout,\n", + " is_selfplay=1)\n", + " self.file_path = file_path # 存储训练参数文件位置\n", + " self.episode_len = 0\n", + "\n", + " def get_equi_data(self, play_data):\n", + " \"\"\"通过旋转和翻转来增加数据集\n", + " play_data: [(state, mcts_prob, winner_z), ..., ...]\n", + " \"\"\"\n", + " extend_data = []\n", + " for state, mcts_porb, winner in play_data:\n", + " for i in [1, 2, 3, 4]:\n", + " # 逆时针旋转\n", + " equi_state = np.array([np.rot90(s, i) for s in state])\n", + " equi_mcts_prob = np.rot90(np.flipud(\n", + " mcts_porb.reshape(self.board_height, self.board_width)), i)\n", + " extend_data.append((equi_state,\n", + " np.flipud(equi_mcts_prob).flatten(),\n", + " winner))\n", + " # 水平翻转\n", + " equi_state = np.array([np.fliplr(s) for s in equi_state])\n", + " equi_mcts_prob = np.fliplr(equi_mcts_prob)\n", + " extend_data.append((equi_state,\n", + " np.flipud(equi_mcts_prob).flatten(),\n", + " winner))\n", + " return extend_data\n", + "\n", + " def collect_selfplay_data(self, n_games=1):\n", + " \"\"\"收集自我博弈数据进行训练\"\"\"\n", + " for i in range(n_games):\n", + " winner, play_data = self.game.start_play_train(self.mcts_player, temp=self.temp)\n", + " play_data = list(play_data)\n", + " self.episode_len = len(play_data)\n", + " # 增加数据\n", + " play_data = self.get_equi_data(play_data)\n", + " self.data_buffer.extend(play_data)\n", + "\n", + " def update_policy_value_net(self):\n", + " \"\"\"更新策略价值网络\"\"\"\n", + " mini_batch = random.sample(self.data_buffer, self.batch_size)\n", + " state_batch = [data[0] for data in mini_batch]\n", + " \n", + " state_batch= np.array( state_batch).astype(\"float32\")\n", + " \n", + " mcts_probs_batch = [data[1] for data in mini_batch]\n", + " mcts_probs_batch= np.array( mcts_probs_batch).astype(\"float32\")\n", + " \n", + " winner_batch = [data[2] for data in mini_batch]\n", + " winner_batch= np.array( winner_batch).astype(\"float32\")\n", + " \n", + " old_probs, old_v = self.policy_value_net.policy_value_evaluate(state_batch)\n", + " loss = kl = 0\n", + " for i in range(self.epochs):\n", + " loss = self.policy_value_net.train_step(\n", + " state_batch,\n", + " mcts_probs_batch,\n", + " winner_batch,\n", + " self.learn_rate * self.lr_multiplier)\n", + " new_probs, new_v = self.policy_value_net.policy_value_evaluate(state_batch)\n", + " kl = np.mean(np.sum(old_probs * (\n", + " np.log(old_probs + 1e-10) - np.log(new_probs + 1e-10)),\n", + " axis=1)\n", + " )\n", + " if kl > self.kl_targ * 4: # early stopping if D_KL diverges badly\n", + " break\n", + " # 自适应调节学习率\n", + " if kl > self.kl_targ * 2 and self.lr_multiplier > 0.1:\n", + " self.lr_multiplier /= 1.5\n", + " elif kl < self.kl_targ / 2 and self.lr_multiplier < 10:\n", + " self.lr_multiplier *= 1.5\n", + " return loss\n", + "\n", + " def evaluate_policy_value_net(self, n_games=10):\n", + " \"\"\"\n", + " 通过与纯的MCTS算法对抗来评估训练的策略\n", + " 注意:这仅用于监控训练进度\n", + " \"\"\"\n", + " current_mcts_player = MCTSPlayer(self.policy_value_net.policy_value_fn,\n", + " c_puct=self.c_puct,\n", + " n_playout=self.n_playout)\n", + " pure_mcts_player = MCTSPlayer(c_puct=5,\n", + " n_playout=self.pure_mcts_playout_num,\n", + " mode='eval')\n", + " win_cnt = defaultdict(int)\n", + " for i in range(n_games):\n", + " winner = self.game.start_play_evaluate(current_mcts_player,\n", + " pure_mcts_player,\n", + " start_player=i % 2)\n", + " win_cnt[winner] += 1\n", + " win_ratio = 1.0 * (win_cnt[1] + 0.5 * win_cnt[-1]) / n_games\n", + " print(\"num_playouts:{}, win: {}, lose: {}, tie:{}\".format(\n", + " self.pure_mcts_playout_num,\n", + " win_cnt[1], win_cnt[2], win_cnt[-1]))\n", + " return win_ratio\n", + "\n", + " def run(self):\n", + " \"\"\"开始训练\"\"\"\n", + " root = os.getcwd()\n", + "\n", + " dst_path = os.path.join(root, self.file_path)\n", + "\n", + " if not os.path.exists(dst_path):\n", + " os.makedirs(dst_path)\n", + "\n", + " loss_list = []\n", + " try:\n", + " for i in range(self.game_batch_num):\n", + " self.collect_selfplay_data(self.play_batch_size)\n", + " if len(self.data_buffer) > self.batch_size:\n", + " loss = self.update_policy_value_net()\n", + " loss_list.append(loss)\n", + "\n", + " if (i + 1) % 50 == 0:\n", + " print(\"batch i:{}, episode_len:{}, loss :{},\".format(i + 1, self.episode_len, loss,))\n", + " self.policy_value_net.save_model(os.path.join(dst_path, 'current_policy_step.model'))\n", + " # 检查当前模型的性能,保存模型的参数\n", + " if (i + 1) % self.check_freq == 0:\n", + " print(\"current self-play batch: {}\".format(i + 1))\n", + " win_ratio = self.evaluate_policy_value_net()\n", + " self.policy_value_net.save_model(os.path.join(dst_path, 'current_policy.model'))\n", + " if win_ratio > self.best_win_ratio:\n", + " print(\"New best policy!!!!!!!!\")\n", + " self.best_win_ratio = win_ratio\n", + " # 更新最好的策略\n", + " self.policy_value_net.save_model(os.path.join(dst_path, 'best_policy.model'))\n", + " if (self.best_win_ratio == 1.0 and\n", + " self.pure_mcts_playout_num < 8000):\n", + " self.pure_mcts_playout_num += 1000\n", + " self.best_win_ratio = 0.0\n", + " except KeyboardInterrupt:\n", + " print('\\n\\rquit')\n", + " finally:\n", + " return loss_list\n", + "\n", + "\n", + "if __name__ == '__main__':\n", + " device = paddle.get_device() \n", + " paddle.set_device(device)\n", + " # model_path = 'model_ygh/best_policy.model'\n", + " # model_path = 'dist/current_policy.model'\n", + "\n", + " # training_pipeline = TrainPipeline(model_path)\n", + " training_pipeline = TrainPipeline(None)\n", + " loss_list = training_pipeline.run()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "## 6. 训练结果与展示:\n", + "\n", + "### 训练过程\n", + "![](https://ai-studio-static-online.cdn.bcebos.com/870e8e440d4744c69ca10a3c80db77684227c25ae1c14d2e9ae88b25ffea32be)\n", + "\n", + "\n", + "### 最终下棋的效果\n", + "![](https://ai-studio-static-online.cdn.bcebos.com/225bd09c070c4177814a408689cb45c0ac8ca042f4e74e81897fc41cb79bc833)\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "py35-paddle1.2.0" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.4" + } + }, + "nbformat": 4, + "nbformat_minor": 4 } \ No newline at end of file diff --git a/docs/templates/layout.html b/docs/templates/layout.html index 5091eb32eae..f65da40df43 100644 --- a/docs/templates/layout.html +++ b/docs/templates/layout.html @@ -2,7 +2,7 @@ {# Import the theme's layout. #} {% extends "!layout.html" %} -{# SIDE NAV, TOGGLES ON MOBILE #} +{# SIDE NAV, TOGGLES ON MOBILE #} {% block menu %}