重构论文结构,更新章节内容
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
\input{sections/00_abstract}
|
||||
\input{sections/01_introduction.tex}
|
||||
\input{sections/02_problem_analyze.tex}
|
||||
\input{sections/03_training.tex}
|
||||
\input{sections/03_numerical_design.tex}
|
||||
\input{sections/04_training.tex}
|
||||
|
||||
\end{document}
|
||||
|
||||
@@ -47,3 +47,4 @@
|
||||
% 其他设置
|
||||
\setcounter{tocdepth}{3} % 目录深度
|
||||
\setcounter{secnumdepth}{3} % 章节编号深度
|
||||
|
||||
|
||||
@@ -8,3 +8,4 @@
|
||||
\end{abstract}
|
||||
|
||||
\textbf{关键词:} 2048游戏、深度强化学习、相对位置编码、self-attention、估值策略网络
|
||||
|
||||
|
||||
@@ -12,12 +12,9 @@
|
||||
\item 难以处理不同位置间的长距离依赖关系
|
||||
\end{itemize}
|
||||
|
||||
Self-attention机制的出现为解决这些问题提供了新的思路。通过引入2D相对位置编码,我们可以:
|
||||
\begin{itemize}
|
||||
\item 显式建模网格中任意两个位置间的关系
|
||||
\item 捕获全局的状态信息
|
||||
\item 学习位置无关的特征表示
|
||||
\end{itemize}
|
||||
Self-attention机制的出现为解决这些问题提供了新的思路。
|
||||
通过引入2D相对位置编码,我们可以在捕获全局状态信息的同时,对棋盘任意两个位置的关系进行建模,
|
||||
同时学习到位置无关的特征表示。
|
||||
|
||||
\subsection{符号定义}
|
||||
\begin{itemize}
|
||||
|
||||
@@ -18,14 +18,10 @@
|
||||
|
||||
为了解决此问题,我们可以对棋盘状态进行对数变换。
|
||||
令棋盘状态为一个矩阵 $K \in \mathbb{N}$,
|
||||
其中 $K_{i,j}$ 表示在 $(i,j)$ 位置的数字,空位记为 $0$。
|
||||
其中 $K_{i,j}$ 表示在 $(i,j)$ 位置的数字,空位记为 $1$ (2048游戏中不会有1出现,同时用1做掩码无需额外判断)。
|
||||
我们定义一个新的状态表示矩阵 $K'$,其元素 $K'_{i,j}$ 通过以下映射获得:
|
||||
\begin{equation}
|
||||
K'_{i,j} =
|
||||
\begin{cases}
|
||||
\log_2(K_{i,j}) & \text{if } K_{i,j} > 0 \\
|
||||
0 & \text{if } K_{i,j} = 0
|
||||
\end{cases}
|
||||
K'_{i,j} = \log_2(K_{i,j})
|
||||
\end{equation}
|
||||
通过这种对数变换,我们可以将指数增长的数值尺度压缩到一个线性、紧凑的整数范围。
|
||||
这种方法同样适用于任意边长的矩形棋盘。
|
||||
@@ -119,4 +115,6 @@
|
||||
\begin{equation}
|
||||
V(N) = (\log_2(N) - 1) \cdot N
|
||||
\end{equation}
|
||||
|
||||
\begin{equation}
|
||||
V(N') = (N'-1) \cdot 2^{N'} \label{eq:log_value_formula}
|
||||
\end{equation}
|
||||
|
||||
59
paper/sections/03_numerical_design.tex
Normal file
59
paper/sections/03_numerical_design.tex
Normal file
@@ -0,0 +1,59 @@
|
||||
\section{数值设计}
|
||||
|
||||
我们使用\texttt{SQLite}存储数据,这是因为\texttt{SQLite}在反序列化对象时的性能消耗可以接受,其相关生态也更加丰富和成熟。
|
||||
|
||||
\subsection{棋盘状态表示}
|
||||
|
||||
如前文所言,对于一个$H \times W$的2048游戏棋盘,其方块数字的最大值$N_{\text{max}} = 2^{(H \times W + 1)}$,
|
||||
对数变化后的方块最大值即为$N'_{\text{max}} = H \times W + 1$。
|
||||
对于$10 \times 10$的棋盘,$N'_{\text{max}} = 101$,\texttt{INT8}足以表示其范围。
|
||||
|
||||
因此,棋盘状态矩阵可以设计为一个\texttt{numpy.ndarray},数据类型为\texttt{np.int8}。
|
||||
|
||||
\subsection{动作价值表示}
|
||||
|
||||
动作价值,实际上代表当前动作进行到终局时,棋盘的状态价值。
|
||||
|
||||
数字$N$的累计价值为 $V(N) = (\log_2(N) - 1) \cdot N$ 。
|
||||
对于一个$H \times W$的棋盘,其方块数字的最大值$N_{\text{max}} = 2^{(H \times W + 1)}$,则该方块的最大累计价值为:
|
||||
\begin{equation}
|
||||
V(N_{\text{max}}) = (\log_2(2^{(H \times W + 1)}) - 1) \cdot 2^{(H \times W + 1)} = (H \times W) \cdot 2^{(H \times W + 1)}
|
||||
\end{equation}
|
||||
|
||||
整个棋盘的最大累计价值理论上为:
|
||||
\begin{align}
|
||||
V_{\text{total}} &= \sum_{i=1}^{H} \sum_{j=1}^{W} V(i,j) \\
|
||||
&= V(N_{\text{max}}) + V(N_{\text{max}}/2) + V(N_{\text{max}}/4) + \dots + V(8) + V(4) \\
|
||||
&\lesssim V(2 \times N_{\text{max}}) \\
|
||||
&= (H \times W + 1) \cdot 2^{(H \times W + 2)}
|
||||
\end{align}
|
||||
|
||||
对于一个$4 \times 4$的棋盘,其最大累计价值为$17 \times 2^{18} = 4,456,448$,虽然在\texttt{INT32}范围内,但当拓展到$10 \times 10$的棋盘时,其累计价值上界为$101 \times 2^{102} \approx 5.15 \times 10^{32}$,远超\texttt{INT64}的表示范围(约$9.22 \times 10^{18}$)。
|
||||
|
||||
然而,在实际游戏过程中,$V_{\text{total}}$的取值具有高度的稀疏性:
|
||||
由于2048游戏的合并规则,棋盘上的数字呈现指数级分布,大部分位置的数值相对较小,
|
||||
只有少数位置达到较高数值。因此,实际的累计价值远小于理论上界。
|
||||
同时,相同的分数差距,随着总分的增长,其在游戏能力上的区分度会逐渐降低。
|
||||
通过对数变换$\log_2(V_{\text{total}})$,可以将数值范围压缩至$[0, \log_2(101 \times 2^{102})] = [0, 108.66]$,
|
||||
完全在\texttt{FP32}的有效精度范围内,且能保持足够的数值精度用于价值函数的区分。
|
||||
|
||||
因此,动作价值矩阵可以设计为一个\texttt{numpy.ndarray},数据类型为\texttt{np.float32},存储对数变换后的价值。
|
||||
|
||||
\subsection{最大价值(\texttt{MAX\_VALUE})}
|
||||
|
||||
这是一个64位高精度浮点数,它用于准确地代表当前状态-策略对的价值,即这个策略所模拟到的最高分。
|
||||
它不参与训练和推理,当传入的状态-策略对与现有的缓存中的某个状态重复时,这个值用于判断是否更新缓存中的价值向量。
|
||||
|
||||
我们认为具有更高终局分数的状态-策略对更有价值。
|
||||
|
||||
\subsection{棋盘尺寸}
|
||||
|
||||
这是一个16位整数,用于代表棋盘的尺寸。其前8位二进制为代表棋盘的高度,后8位代表棋盘的宽度。
|
||||
|
||||
例如,一个$7 \times 5$的棋盘,其尺寸编码为\texttt{0b0000011100000101},或是\texttt{0x0705}。
|
||||
|
||||
\subsection{注释}
|
||||
|
||||
游戏策略代码从设计之初就考虑到使用对数(0,1,2,3……)即$N'$表示棋盘上的数字,因此无需进行转换。
|
||||
|
||||
但是分数计算需要使用公式~\eqref{eq:log_value_formula}计算后取对数。
|
||||
@@ -1,60 +0,0 @@
|
||||
\section{模型训练}
|
||||
为了训练一个强大的估值决策模型,我们采用蒙特卡洛树搜索(MCTS)生成高质量的训练数据,并设计一个轻量级的残差卷积网络(RNCNN)进行初步学习与迭代。
|
||||
|
||||
\subsection{蒙特卡洛树搜索策略}
|
||||
采用纯MCTS(Pure MCTS)为初始模型生成训练数据。MCTS的每次迭代包含四个核心步骤,以当前棋盘状态为根节点,不断扩展搜索树。
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{选择 (Selection)}: 从根节点开始,根据UCT(Upper Confidence bounds applied to Trees)公式递归选择子节点,直到达到一个未完全扩展的叶子节点。UCT公式平衡了节点的探索(Exploration)与利用(Exploitation):
|
||||
\[ \text{UCT} = \underset{i}{\arg\max} \left( \bar{v}_i + C \cdot \sqrt{\frac{\ln(N)}{n_i}} \right) \]
|
||||
其中 $\bar{v}_i$ 是子节点 $i$ 的平均价值,$N$ 是父节点访问次数,$n_i$ 是子节点 $i$ 访问次数,$C$ 是探索常数。
|
||||
|
||||
\item \textbf{扩展 (Expansion)}: 当选择过程到达一个叶子节点 $L$ 时,如果该节点代表的局面不是终局,则为其创建一个或多个子节点,对应于从 $L$ 出发所有合法的移动。
|
||||
|
||||
\item \textbf{模拟 (Simulation)}: 从新扩展的子节点中选择一个,开始进行模拟(也称Rollout)。在此阶段,我们采用快速的随机策略(例如,在所有合法移动中随机选择一个)持续进行游戏,直到达到终局状态。终局的分数将作为本次模拟的价值。
|
||||
|
||||
\item \textbf{反向传播 (Backpropagation)}: 将模拟得到的终局分数 $v$ 从该叶子节点开始,沿着选择路径反向传播回根节点。路径上的每一个节点都会更新其访问次数 $n$ 和累积价值 $V$,并重新计算其平均价值 $\bar{v} = V/n$。
|
||||
\end{itemize}
|
||||
|
||||
\subsection{数据结构}
|
||||
通过大量的MCTS模拟,我们将叶子节点的信息转换为神经网络可学习的训练样本。每个样本包含:
|
||||
\begin{itemize}
|
||||
\item 棋盘状态 $S$:一个 $H \times W$ 的矩阵,代表游戏局面。
|
||||
\item 策略-价值对 $(\pi, v)$:其中 $\pi$ 是从状态 $S$ 出发的一个合法移动(例如:上、下、左、右),$v$ 是在该分支下通过后续模拟所能达到的最高分。
|
||||
\end{itemize}
|
||||
我们将 $(S, \pi)$ 作为键(key),当一次完整的MCTS模拟结束后,解析所有扩展出的叶子节点。对于每个叶子节点 $(S, \pi)$,其价值 $v$ 等于它后续所有模拟游戏中的最高分。
|
||||
\begin{itemize}
|
||||
\item 如果一个键 $(S, \pi)$ 没有命中缓存,则写入 $(S, \pi) \rightarrow v$。
|
||||
\item 如果一个叶子节点的新分数大于缓存中的值,则更新 $(S, \pi) \rightarrow v$。
|
||||
\end{itemize}
|
||||
在进行指定次数的MCTS模拟后,导出所有缓存的 $(S, \pi) \rightarrow v$ 对,作为最终的训练数据集。
|
||||
|
||||
\subsection{残差卷积网络L0}
|
||||
RNCNN\_L0 是一个极小的、用于快速推理和迭代的估值决策模型。其设计目标是在有限的计算资源下,学习到基本的局面评估能力。其结构定义如下:
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{输入层 (Input Layer)}:
|
||||
棋盘状态 $S$(一个 $H \times W$ 的矩阵)首先进行特征化处理。我们将每个格子的值 $V$ 转换为 $C$ 个特征平面(channel),每个平面代表一个特定的瓦片值(例如,$V \rightarrow \log_2(V)$,然后进行独热编码)。输入张量的维度为 $(H, W, C)$。
|
||||
|
||||
\item \textbf{卷积主干 (Convolutional Body)}:
|
||||
输入张量首先通过一个卷积层,然后送入一个由 $N$ 个残差块(Residual Block)组成的序列。
|
||||
\begin{itemize}
|
||||
\item \textbf{初始卷积层}: 一个$3 \times 3$的卷积核,输出64个特征图,进行批量归一化(Batch Normalization)和ReLU激活。
|
||||
\item \textbf{残差块}: 每个残差块包含两个卷积层。输入通过第一个 $3 \times 3$ 卷积层(BN+ReLU),再通过第二个 $3 \times 3$ 卷积层(BN),然后将结果与块的输入相加(残差连接),最后通过一个ReLU激活函数。所有卷积层保持64个通道数。
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{输出头 (Output Head)}:
|
||||
残差主干的输出特征图被送入一个最终的输出模块,该模块直接预测四个动作的价值。
|
||||
\begin{itemize}
|
||||
\item 一个 $1 \times 1$ 的卷积层,将通道数从64降至16,进行BN和ReLU激活。
|
||||
\item 将特征图展平(Flatten)成一维向量。
|
||||
\item 一个全连接层(Fully Connected Layer),将向量映射到4个输出神经元,分别对应四个移动方向(上、下、左、右)的预测价值。
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
模型的损失函数采用均方误差(Mean Squared Error),计算网络预测的四通道价值与MCTS生成数据中对应动作的价值 $v$ 之间的差距。
|
||||
|
||||
\subsection{模型初始化}
|
||||
我们计划在3$\times$3的小棋盘上,依靠纯蒙特卡洛树的快速搜索能力,生成大量的初始数据。小棋盘状态空间较小,MCTS能更快地收敛到有意义的策略,为模型提供高质量的初始训练样本。
|
||||
|
||||
\subsection{L0迭代}
|
||||
在纯蒙特卡洛生成的数据上学习到的大量初始数据,将提供给RNCNN\_L0进行监督学习训练。训练完成的L0模型可以反过来指导MCTS中的选择和模拟阶段,形成一个自我博弈(self-play)的增强回路,不断迭代优化模型性能。
|
||||
99
paper/sections/04_training.tex
Normal file
99
paper/sections/04_training.tex
Normal file
@@ -0,0 +1,99 @@
|
||||
\section{模型训练}
|
||||
为了训练一个强大的估值决策模型,我们采用蒙特卡洛树搜索(MCTS)生成高质量的训练数据,
|
||||
并设计一个轻量级的残差卷积网络(RNCNN)进行初步学习与迭代。
|
||||
|
||||
\subsection{蒙特卡洛树搜索策略}
|
||||
我们采用纯蒙特卡洛树搜索(Pure MCTS)为初始模型生成训练数据。
|
||||
MCTS的每次迭代包含四个核心步骤,以当前棋盘状态为根节点,不断扩展搜索树。
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{选择 (Selection)}:
|
||||
%% 修改点:将UCT公式本身与选择策略分开,表述更严谨
|
||||
从根节点开始,根据UCT(Upper Confidence bounds applied to Trees)选择策略递归选择子节点,
|
||||
直到达到一个未完全扩展的叶子节点。该策略会选择具有最大UCT值的子节点 $i$。节点 $i$ 的UCT值计算公式为:
|
||||
\[ \text{UCT}_i = \bar{v}_i + C \cdot \sqrt{\frac{\ln(N)}{n_i}} \]
|
||||
其中,$\bar{v}_i$ 是子节点 $i$ 的平均回报(mean return),$N$ 是其父节点的访问次数,$n_i$ 是子节点 $i$ 的访问次数,
|
||||
$C$ 是平衡探索与利用的常数。$i$ 遍历当前节点所有已发现的子节点。
|
||||
|
||||
\item \textbf{扩展 (Expansion)}: 当选择过程到达一个叶子节点 $L$ 时,若该节点所代表的局面非终局,
|
||||
则为其创建一个或多个子节点,对应于从状态 $L$ 出发的所有合法走法。
|
||||
|
||||
\item \textbf{模拟 (Simulation)}:
|
||||
从新扩展的子节点中选择一个,开始进行快速走子(Rollout)。在此阶段,我们采用一个快速的默认策略
|
||||
(例如,在所有合法移动中均匀随机选择)进行游戏,直至达到终局状态。终局的收益(如 胜/负/平 对应 +1/-1/0,或游戏得分)
|
||||
被记录为本次模拟的回报 $z$。
|
||||
|
||||
\item \textbf{反向传播 (Backpropagation)}:
|
||||
将模拟得到的回报 $z$ 从该叶子节点开始,沿选择路径反向传播至根节点。
|
||||
路径上的每个节点 $j$ 都会更新其统计量:访问次数 $n_j \leftarrow n_j + 1$,
|
||||
总回报 $V_j \leftarrow V_j + z$。其平均回报也相应更新为 $\bar{v}_j = V_j / n_j$。
|
||||
\end{itemize}
|
||||
|
||||
\subsection{数据结构}
|
||||
通过大量的MCTS模拟,我们将每个访问过的状态及其MCTS分析结果转化为神经网络可学习的训练样本。
|
||||
|
||||
我们为每个经过充分模拟的状态 $S$ 生成一个训练目标。
|
||||
该目标是一个动作价值向量 $\mathbf{\pi}(S) \in \mathbb{R}^{|\mathcal{A}|}$,
|
||||
其中 $\mathcal{A} = \{\text{上, 下, 左, 右}\}$ 是动作空间。
|
||||
向量的每个分量 $\pi_a(S)$ 代表在状态 $S$ 下,
|
||||
执行动作 $a$ 后,MCTS估算出的期望回报。
|
||||
|
||||
\begin{itemize}
|
||||
\item 对于不合法的移动或在MCTS中未被探索到的动作 $a$,
|
||||
我们将其价值设为一个特殊的掩码值$\pi_a = -1$,并在计算损失函数时忽略这些项。
|
||||
\item 所有状态 $S$ 及其对应的动作价值向量 $\mathbf{\pi}(S)$ 被存储在一个持久化的缓存中。
|
||||
该缓存结构为 $\text{Cache}: \mathcal{S} \rightarrow \mathbb{R}^{|\mathcal{A}|}$,
|
||||
其中 $\mathcal{S}$ 是所有遇到过的状态集合。为了高效检索,我们使用状态的哈希值作为键。
|
||||
\end{itemize}
|
||||
|
||||
缓存的更新策略如下:
|
||||
\begin{itemize}
|
||||
\item \textbf{初始写入:} 若状态 $S$ 不在缓存中,则在完成对 $S$ 的MCTS模拟后,将映射 $S \mapsto \mathbf{\pi}(S)$ 添加到缓存中。
|
||||
\item \textbf{缓存更新:}
|
||||
我们定义状态 $S$ 的(估计)价值为其最优动作的价值,即 $v_{\text{state}}(S) = \max_{a \in \mathcal{A}} \pi_a(S)$。
|
||||
若状态 $S$ 已在缓存中,但在新一轮MCTS模拟后得到了新的价值向量 $\mathbf{\pi}'(S)$,且其状态价值 $v'_{\text{state}}(S) > v_{\text{state}}(S)$,则我们将缓存中的条目更新为 $S \mapsto \mathbf{\pi}'(S)$。此策略旨在保留能导向更优结果的搜索信息。
|
||||
\end{itemize}
|
||||
|
||||
最终,我们将缓存中的数据转换为一个训练集 $\mathcal{D}$。对于缓存中的每一个 $(S, \mathbf{\pi}(S))$ 对,
|
||||
我们将其展开为多个独立的样本,形成训练集 $\mathcal{D} = \{(S, a, \pi_a(S)) \mid \forall S \in \text{Cache}, \forall a \in \text{valid\_actions}(S)\}$。
|
||||
|
||||
\subsection{残差卷积网络L0}
|
||||
RNCNN\_L0 是一个为快速推理和迭代设计的轻量级估值决策模型。其结构如下:
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{输入层 (Input Layer)}:
|
||||
棋盘状态 $S \in \mathbb{R}^{H \times W}$ 首先经过特征化处理。
|
||||
将每个非空格子的值 $V$ 通过对数函数(如 $k = \log_2(V)$)映射为整数索引,
|
||||
再进行独热编码,形成 $C$ 个特征平面(channel)。最终的输入张量维度为 $(H, W, C)$,其中 $C$ 是编码后的特征总数。
|
||||
|
||||
\item \textbf{卷积主干 (Convolutional Body)}:
|
||||
输入张量经过以下处理:
|
||||
\begin{itemize}
|
||||
\item \textbf{初始卷积层}: 一个 $3 \times 3$ 的填充卷积层,保持空间维度不变,后接批量归一化(Batch Normalization)和ReLU激活函数。
|
||||
\item \textbf{残差块序列}: 两个连续的残差块,每个残差块包含两个卷积层。输入首先通过一个 $3 \times 3$ 卷积层(BN+ReLU),
|
||||
再通过第二个 $3 \times 3$ 卷积层(BN),然后将结果与块的输入进行逐元素相加(残差连接),最后通过一个ReLU激活函数。
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{输出头 (Output Head)}:
|
||||
卷积主干输出的特征图被送入一个专门设计的输出模块:
|
||||
\begin{itemize}
|
||||
\item 四个并行的 $1 \times 1$ 卷积层,每个对应一个方向(上、下、左、右),将特征图扩展为 $(H, W, 4)$ 的张量。
|
||||
\item 对每个方向通道分别应用全局最大池化(Global Max Pooling),得到四个标量值,分别对应四个方向的动作价值。
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
模型的损失函数采用均方误差(Mean Squared Error, MSE)。
|
||||
对于训练集中的每个样本 $(S, a, \pi_a(S))$,损失函数计算网络对状态 $S$ 预测的动作 $a$ 的价值与MCTS提供的目标价值 $\pi_a(S)$ 之间的差距。
|
||||
|
||||
\subsection{模型初始化}
|
||||
我们在 $3 \times 3$ 的小棋盘上,依靠纯MCTS的快速搜索能力,生成大量的初始训练数据。
|
||||
通过约200,000次完整的self-play对局(每局平均约50步),我们累积了约1000万个棋盘状态对作为训练样本。
|
||||
由于小棋盘的状态空间有限,MCTS能够更快地收敛到高质量的策略,从而为模型提供优质的初始训练样本。
|
||||
|
||||
\subsection{L0迭代与迁移}
|
||||
RNCNN\_L0模型在纯MCTS生成的初始数据上进行监督学习训练。
|
||||
训练完成的L0模型可以反过来指导MCTS的选择与模拟阶段,以替代纯随机的Rollout策略,
|
||||
从而形成一个自我对弈(self-play)的强化学习回路,持续迭代并提升模型性能。
|
||||
|
||||
L0模型的轻量级设计使其能够直接迁移到 $4 \times 4$ 和 $5 \times 5$ 的棋盘上运行,
|
||||
为更大规模棋盘上的MCTS提供基础的估值和策略指导。由于其架构中使用了填充卷积和全局池化操作,
|
||||
模型可以自然地适应不同尺寸的输入,无需额外的结构调整。
|
||||
Reference in New Issue
Block a user