ttt-rl

GitHub
578 60 较难 1 次阅读 1周前BSD-2-Clause其他
AI 解读 由 AI 自动生成,仅供参考

ttt-rl 是一个用纯 C 语言编写的井字棋强化学习示例项目。它旨在通过一个极简的实战案例,帮助开发者从零开始理解强化学习与神经网络的核心原理。

该项目解决了传统机器学习示例往往依赖庞大框架(如 PyTorch)、代码复杂且难以窥探底层细节的痛点。ttt-rl 完全不使用任何外部库,仅在 400 行代码内实现了一个完整的智能体。其独特之处在于“白板”学习模式:神经网络初始权重随机,除了基本规则外对游戏策略一无所知,仅依靠胜负平结果的奖励信号进行自我进化。经过约 200 万局与随机对手的对抗训练,它能达到近乎完美的胜率,几乎不再输掉比赛。

代码内部注释丰富,平均每两行代码就有一行解释,并特别标注了关键的学习机会点。这使得 ttt-rl 非常适合希望深入探究 AI 算法本质的程序员、学生或研究人员作为入门教材。如果你厌倦了黑盒式的调用,想亲手拆解并理解强化学习如何从无到有地掌握技能,这个轻量级项目将是极佳的学习起点。

使用场景

某高校计算机系讲师计划开设强化学习入门课,希望学生能透过代码直观理解算法核心,而非被复杂的框架依赖劝退。

没有 ttt-rl 时

  • 教学门槛过高:现有示例多基于 PyTorch 等大型框架,学生需先耗费数周配置环境和学习 API,难以聚焦算法逻辑本身。
  • 代码黑盒严重:动辄数千行的封装代码掩盖了神经网络权重更新、奖励反馈等关键细节,初学者如同“盲人摸象”。
  • 缺乏从零构建的视角:学生习惯了调用现成库,无法理解如何在不依赖外部库的情况下,仅用几百行 C 代码实现完整的智能体训练闭环。
  • 调试与修改困难:庞大的依赖树使得在嵌入式设备或受限环境中演示变得几乎不可能,限制了实验场景的灵活性。

使用 ttt-rl 后

  • 极简上手体验:ttt-rl 仅用不到 400 行无依赖的 C 代码即可运行,学生编译即用,将精力完全集中在强化学习原理上。
  • 逻辑透明可见:从冷启动随机权重到仅凭输赢信号进行“白板”学习,每一行代码都清晰展示了状态表示、网络结构及奖励机制的实现细节。
  • 完整复现算法精髓:通过观察 ttt-rl 在与随机对手博弈 200 万局后达到近乎零失误的过程,学生能亲眼见证智能体如何从无知识状态进化为完美玩家。
  • 灵活的教学扩展:由于代码短小精悍且无外部依赖,教师可轻松带领学生在任何环境下修改参数、调整网络层数,甚至将其移植到其他轻量级项目中。

ttt-rl 通过极致精简的代码实现,将强化学习从复杂的框架束缚中解放出来,成为连接理论概念与工程实现的完美桥梁。

运行环境要求

操作系统
  • Linux
  • macOS
  • Windows
GPU

不需要 GPU,仅使用 CPU 运行

内存

未说明(因程序极小且无外部依赖,常规内存即可)

依赖
notes该工具完全由 C 语言编写,无任何外部库依赖。只需标准 C 编译器(如 gcc 或 clang)即可编译运行。编译命令示例:cc ttt.c -o ttt -O3 -Wall -W -ffast-math -lm。程序通过自我对弈进行强化学习,默认对弈 15 万局后进入人机交互模式。
python不需要 Python
ttt-rl hero image

快速开始

基于强化学习的井字棋游戏

唯一获胜的走法,就是不走棋

这段代码实现了一个神经网络,利用强化学习来学习井字棋的游戏规则——只需与随机对手对弈,仅用不到400行C语言代码完成,且未使用任何外部库。虽然市面上已有大量基于强化学习的示例代码,这些代码或以PyTorch为框架编写,或采用其他机器学习框架,但我真正想实现的是从零开始完整地构建这个系统,让每一部分都清晰易懂、一目了然。

尽管这段代码只是一个“玩具”级的程序,旨在帮助有兴趣的读者掌握强化学习的基础知识,但它却充分展示了强化学习在无需事先了解游戏规则的情况下,仍能高效学习并掌握新事物的强大能力:

  1. 采用冷启动策略:神经网络的权重初始化为随机值。
  2. “白手起家”的学习方式:程序中并未预设任何关于游戏规则的知识,除了明确X和O不能落在已被占用的方格中;同时,当连续出现三个相同符号,或所有方格都被填满时,即判定为胜利或平局。
  3. 训练神经网络所依赖的唯一信号,便是游戏的奖励:赢、输、平。

在我过去使用Kilo编辑器Picol解释器的经历中,我注意到:对于那些希望深入了解新领域(尤其是年轻程序员)的开发者来说,简洁、无依赖、结构清晰、注释详尽且“极其短小”的C语言程序,往往是一个很好的入门起点。因此,为了向萨顿和巴托颁发图灵奖致敬,我决定亲手编写这段代码。

要运行此程序,请使用以下命令进行编译:

cc ttt.c -o ttt -O3 -Wall -W -ffast-math -lm

然后执行:

./ttt

默认情况下,程序将与随机对手对弈(即每次移动时,对手会随机在随机位置投放“X”)。玩家将进行15万局对弈。随后,程序会启动一个命令行界面,供用户与人类玩家对弈。您可以通过在第一个参数中指定想要与随机对手对弈的局数,来调整游戏的对局数量:

./ttt 2000000

在运行200万局后(所需时间仅几秒),程序通常已不再输掉任何一局。

经过多次自我对弈,程序最终达到了近乎完美的水平:

Games: 2000000, Wins: 1756049 (87.8%)
                    Losses: 731 (0.0%)
                    Ties: 243220 (12.2%)

需要注意的是,有些运行结果比其他运行更幸运,这很可能与神经网络的权重初始化有关。如果您无法达到0局输棋,不妨多运行几次程序。

程序的工作原理

代码力求简单,并附有详尽的注释,每两行代码大约只有一行注释:理解其工作原理应该相当容易。不过,在本README中,我仍将重点阐述几个关键点。此外,请务必仔细阅读代码内部的“LEARNING OPPORTUNITY”注释——在那里,我尝试突出显示神经网络领域中一些重要的研究成果或技术,这些内容或许值得您进一步深入研究。

方格板的表示

游戏的状态仅由以下数据构成:

typedef struct {
    char board[9];          // 可以是“.”(空格)或“X”、“O”。
    int current_player;     // 0 表示人类玩家(X),1 表示电脑玩家(O)。
} GameState;

人类玩家和电脑玩家始终按相同的顺序进行游戏:人类先手,电脑则根据人类的走法作出回应。双方也始终使用相同的标记:人类玩家用“X”,电脑玩家用“O”。

方格板本身仅由9个字符表示,具体取决于该方格是否为空,或者已放置“X”或“O”。

神经网络

神经网络的结构非常简单,因为代码的设计初衷就是追求简洁:它仅包含一层隐藏层,而这一层足以很好地模拟这款如此简单的游戏(增加更多层不仅无法加快收敛速度,反而可能使游戏表现更差)。

值得注意的是,井字棋共有5478种可能的状态;默认情况下,我们的神经网络拥有100个隐藏神经元:

  • 18个输入神经元 * 100个隐藏神经元 +
  • 100个隐藏神经元 * 9个输出神经元 +
  • 100个隐藏神经元 + 9个偏置项

总计2809个参数,因此我们的神经网络几乎能够完全记住游戏中的每一个状态。不过,您也可以将隐藏层的神经元数量减少至25个(甚至更少),即便如此,我们的神经网络依然能够以约700个参数左右的规模,出色地完成游戏对弈任务——尽管未必能达到完美水平。

typedef struct {
    // 权重与偏置。
    float weights_ih[NN_INPUT_SIZE * NN_HIDDEN_SIZE];
    float weights_ho[NN_HIDDEN_SIZE * NN_OUTPUT_SIZE];
    float biases_h[NN_HIDDEN_SIZE];
    float biases_o[NN_OUTPUT_SIZE];

    // 为了简化起见,激活函数直接存储在结构体中。
    float inputs[NN_INPUT_SIZE];
    float hidden[NN_HIDDEN_SIZE];
    float raw_logits[NN_OUTPUT_SIZE]; // 激活函数之前的数据。
    float outputs[NN_OUTPUT_SIZE];    // 激活函数之后的数据。
} NeuralNetwork;

激活函数直接存储在神经网络内部,因此计算梯度并进行反向传播的过程极为简单。

我们选择ReLU作为激活函数,因为其导数简单。在本例中,几乎所有其他激活函数都能正常工作。权重初始化并不需要考虑ReLU,它们只是从-0.5到0.5之间随机生成(无需采用He初始化方法)。

输出通过softmax函数计算得出,因为该神经网络本质上会为每一种可能的下一步行动分配概率。理论上,我们应使用交叉熵来计算损失函数,但在实际应用中,我们主要依据对局的结果来评估“智能体”的表现,因此在这里我们只是隐式地使用了交叉熵:

        deltas[i] = output[i] - target[i]

这就是在使用softmax和交叉熵时的梯度计算公式。

强化学习策略

此处采用的奖励策略如下:

    if (winner == 'T') {
        reward = 0.2f;  // 对于平局,给予较小的奖励
    } else if (winner == nn_symbol) {
        reward = 1.0f;  // 对于获胜,给予较大的奖励
    } else {
        reward = -1.0f; // 对于失败,给予负值奖励
    }

在进行奖励时,我们会为神经网络即将移动的所有游戏状态创建奖励,并且针对每个状态,我们都会对获胜的走法进行奖励(不仅仅是最终获胜的那一步,而是我们在赢得比赛的过程中所执行的所有走法),并将其他所有被设置为 0 的走法作为目标输出,而我们要奖励的走法则设置为 1。随后,我们通过一次反向传播过程来更新权重。

对于平局的情况,其奖励机制与胜利时类似,但奖励会被适当缩放。相反,在游戏失败时,我们以将走法设置为 0 的走法作为目标,同时将所有无效走法也设置为 0,并将所有其他有效走法设置为 1/(有效走法数量)

不过,我们还会根据走法完成的时机进行相应的缩放:对于那些接近游戏开局的走法,我们给予较小的奖励;而对于那些在游戏后期(接近游戏结束时)才出现的走法,则会提供更高的奖励:

    float move_importance = 0.5f + 0.5f * (float)move_idx/(float)num_moves;
    float scaled_reward = reward * move_importance;

需要注意的是,上述设计对程序的运行方式产生了显著的影响。 此外,请注意,尽管这一方法在强化学习中看似与“时间差”有相似之处,但实际上并不相同:在这种情况下,我们并没有一种简单的方法来评估单个步骤是否带来了正向或负向的奖励——我们需要等待每一场游戏全部结束。上面提到的时间尺度缩放,只是在网络内部实现的一种编码方式:早期的走法更加开放,而随着游戏进程的推进,我们则需要更加有选择地进行决策。

权重更新

我们仅使用简单的反向传播算法,而代码的设计初衷正是为了清晰地展示:归根结底,程序的工作方式与监督学习非常相似:唯一的区别在于,输入/输出对并非事先已知,而是根据强化学习的奖励策略,实时动态地提供给模型。

如需了解更多信息,请查看相关代码。

未来工作

以下是我尚未进行测试的领域,因为这些内容若未充分测试,可能会因复杂度过高而削弱本程序的教育价值,或者由于时间不足而无法实现。不过,这些领域同样可以成为有趣的练习课题,甚至为其他项目或分支带来启发:

  • 这种方法是否也能应用于四子棋?问题空间的规模要大得多,这将极具研究价值,而且也更贴近实际应用。
  • 通过增加一个额外的输入变量——即即将执行走法的棋子符号(尤其在四子棋中很有用),训练网络以同时应对双方的博弈。这样一来,我们就可以直接利用网络本身作为对手,而非单纯地与随机走法进行对抗。
  • 在上述场景中,实施合理的采样策略:初始阶段,走法应较为随机;随着游戏进程的推进,走法的选择也会逐渐变得更加稳定、更具针对性。
  • 实现 MCTS 算法。

相似工具推荐

ML-For-Beginners

ML-For-Beginners 是由微软推出的一套系统化机器学习入门课程,旨在帮助零基础用户轻松掌握经典机器学习知识。这套课程将学习路径规划为 12 周,包含 26 节精炼课程和 52 道配套测验,内容涵盖从基础概念到实际应用的完整流程,有效解决了初学者面对庞大知识体系时无从下手、缺乏结构化指导的痛点。 无论是希望转型的开发者、需要补充算法背景的研究人员,还是对人工智能充满好奇的普通爱好者,都能从中受益。课程不仅提供了清晰的理论讲解,还强调动手实践,让用户在循序渐进中建立扎实的技能基础。其独特的亮点在于强大的多语言支持,通过自动化机制提供了包括简体中文在内的 50 多种语言版本,极大地降低了全球不同背景用户的学习门槛。此外,项目采用开源协作模式,社区活跃且内容持续更新,确保学习者能获取前沿且准确的技术资讯。如果你正寻找一条清晰、友好且专业的机器学习入门之路,ML-For-Beginners 将是理想的起点。

85k|★★☆☆☆|今天
图像数据工具视频

ragflow

RAGFlow 是一款领先的开源检索增强生成(RAG)引擎,旨在为大语言模型构建更精准、可靠的上下文层。它巧妙地将前沿的 RAG 技术与智能体(Agent)能力相结合,不仅支持从各类文档中高效提取知识,还能让模型基于这些知识进行逻辑推理和任务执行。 在大模型应用中,幻觉问题和知识滞后是常见痛点。RAGFlow 通过深度解析复杂文档结构(如表格、图表及混合排版),显著提升了信息检索的准确度,从而有效减少模型“胡编乱造”的现象,确保回答既有据可依又具备时效性。其内置的智能体机制更进一步,使系统不仅能回答问题,还能自主规划步骤解决复杂问题。 这款工具特别适合开发者、企业技术团队以及 AI 研究人员使用。无论是希望快速搭建私有知识库问答系统,还是致力于探索大模型在垂直领域落地的创新者,都能从中受益。RAGFlow 提供了可视化的工作流编排界面和灵活的 API 接口,既降低了非算法背景用户的上手门槛,也满足了专业开发者对系统深度定制的需求。作为基于 Apache 2.0 协议开源的项目,它正成为连接通用大模型与行业专有知识之间的重要桥梁。

77.1k|★★★☆☆|2天前
Agent图像开发框架

PaddleOCR

PaddleOCR 是一款基于百度飞桨框架开发的高性能开源光学字符识别工具包。它的核心能力是将图片、PDF 等文档中的文字提取出来,转换成计算机可读取的结构化数据,让机器真正“看懂”图文内容。 面对海量纸质或电子文档,PaddleOCR 解决了人工录入效率低、数字化成本高的问题。尤其在人工智能领域,它扮演着连接图像与大型语言模型(LLM)的桥梁角色,能将视觉信息直接转化为文本输入,助力智能问答、文档分析等应用场景落地。 PaddleOCR 适合开发者、算法研究人员以及有文档自动化需求的普通用户。其技术优势十分明显:不仅支持全球 100 多种语言的识别,还能在 Windows、Linux、macOS 等多个系统上运行,并灵活适配 CPU、GPU、NPU 等各类硬件。作为一个轻量级且社区活跃的开源项目,PaddleOCR 既能满足快速集成的需求,也能支撑前沿的视觉语言研究,是处理文字识别任务的理想选择。

74.9k|★★★☆☆|今天
语言模型图像开发框架

awesome-machine-learning

awesome-machine-learning 是一份精心整理的机器学习资源清单,汇集了全球优秀的机器学习框架、库和软件工具。面对机器学习领域技术迭代快、资源分散且难以甄选的痛点,这份清单按编程语言(如 Python、C++、Go 等)和应用场景(如计算机视觉、自然语言处理、深度学习等)进行了系统化分类,帮助使用者快速定位高质量项目。 它特别适合开发者、数据科学家及研究人员使用。无论是初学者寻找入门库,还是资深工程师对比不同语言的技术选型,都能从中获得极具价值的参考。此外,清单还延伸提供了免费书籍、在线课程、行业会议、技术博客及线下聚会等丰富资源,构建了从学习到实践的全链路支持体系。 其独特亮点在于严格的维护标准:明确标记已停止维护或长期未更新的项目,确保推荐内容的时效性与可靠性。作为机器学习领域的“导航图”,awesome-machine-learning 以开源协作的方式持续更新,旨在降低技术探索门槛,让每一位从业者都能高效地站在巨人的肩膀上创新。

72.1k|★☆☆☆☆|2天前
开发框架其他

scikit-learn

scikit-learn 是一个基于 Python 构建的开源机器学习库,依托于 SciPy、NumPy 等科学计算生态,旨在让机器学习变得简单高效。它提供了一套统一且简洁的接口,涵盖了从数据预处理、特征工程到模型训练、评估及选择的全流程工具,内置了包括线性回归、支持向量机、随机森林、聚类等在内的丰富经典算法。 对于希望快速验证想法或构建原型的数据科学家、研究人员以及 Python 开发者而言,scikit-learn 是不可或缺的基础设施。它有效解决了机器学习入门门槛高、算法实现复杂以及不同模型间调用方式不统一的痛点,让用户无需重复造轮子,只需几行代码即可调用成熟的算法解决分类、回归、聚类等实际问题。 其核心技术亮点在于高度一致的 API 设计风格,所有估算器(Estimator)均遵循相同的调用逻辑,极大地降低了学习成本并提升了代码的可读性与可维护性。此外,它还提供了强大的模型选择与评估工具,如交叉验证和网格搜索,帮助用户系统地优化模型性能。作为一个由全球志愿者共同维护的成熟项目,scikit-learn 以其稳定性、详尽的文档和活跃的社区支持,成为连接理论学习与工业级应用的最

65.6k|★☆☆☆☆|今天
开发框架其他数据工具

keras

Keras 是一个专为人类设计的深度学习框架,旨在让构建和训练神经网络变得简单直观。它解决了开发者在不同深度学习后端之间切换困难、模型开发效率低以及难以兼顾调试便捷性与运行性能的痛点。 无论是刚入门的学生、专注算法的研究人员,还是需要快速落地产品的工程师,都能通过 Keras 轻松上手。它支持计算机视觉、自然语言处理、音频分析及时间序列预测等多种任务。 Keras 3 的核心亮点在于其独特的“多后端”架构。用户只需编写一套代码,即可灵活选择 TensorFlow、JAX、PyTorch 或 OpenVINO 作为底层运行引擎。这一特性不仅保留了 Keras 一贯的高层易用性,还允许开发者根据需求自由选择:利用 JAX 或 PyTorch 的即时执行模式进行高效调试,或切换至速度最快的后端以获得最高 350% 的性能提升。此外,Keras 具备强大的扩展能力,能无缝从本地笔记本电脑扩展至大规模 GPU 或 TPU 集群,是连接原型开发与生产部署的理想桥梁。

63.9k|★★☆☆☆|昨天
开发框架数据工具其他