news 2026/9/28 2:02:38

AlphaGoZero实战解析:蒙特卡洛树搜索与神经网络训练的工程实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AlphaGoZero实战解析:蒙特卡洛树搜索与神经网络训练的工程实现

简介:一份用C++与Python实现的AlphaGoZero围棋AI程序,适合有一定深度学习和强化学习基础、希望从代码层面研究围棋AI的开发者,可用于课程设计、毕业设计或入门科研。包内共110个文件,压缩包仅156KB,包含C++源文件(21个.cc、21个.h)、Python脚本、Proto协议定义、conf配置与Bazel构建文件,覆盖网络定义、训练循环、自我对弈和MCTS搜索等模块;其中conf负责运行参数,proto定义数据接口,py脚本承担训练调度,C++源码承载核心搜索与神经网络推理。目前已有472人学习/下载,项目代码基于PhoenixGo-master实践工程整理,策略网络与价值网络共享卷积层、联合优化以及蒙特卡洛树搜索的决策方式均有对应实现。读者可结合md说明文档和构建脚本,梳理数据生成、标准化、损失函数与超参数调优的完整链路,并能通过启动脚本在GPU/CPU环境下运行和二次开发;无论用于算法对比还是工程实践,这套代码都提供了可复现的起点。

1. 一个标题里藏了整套强化学习闭环:AlphaGoZero 论文落地到底要写多少行代码

看到「AlphaGoZero 论文的围棋 AI 程序_C++_Python_下载.zip」这个标题,大部分人的第一反应是「终于有源码包了」。但真照着论文复现过的人都知道,AlphaGoZero 最难的不是神经网络结构,而是把蒙特卡洛树搜索、自我对弈数据生成、策略价值网络训练、C++ 高性能对弈引擎这几块缝成一个能闭环运转的系统。标题里同时出现 C++ 和 Python,恰恰说明这条路的标准做法:用 Python 快速迭代训练逻辑和模型,用 C++ 扛住自我对弈时每秒几千局模拟的算力需求。

这篇笔记直接按照工程落地的顺序来讲——先拆论文要点,再给棋盘实现、MCTS、网络训练的最小可跑代码,最后列我认为最值得警惕的 5 个坑。适合已经会写 C++/Python、想真正跑通围棋 AI 而不是只满足于「下载完解压看看」的开发者。全文不依赖某个特定开源包的内部细节,你照着结构自己搭,也能搭出一个棋力从乱下到能咬住业余初段的系统。

2. 先把论文读成工程清单:AlphaGoZero 的四个模块和选型理由

2.1 论文里没有明说的组件划分:棋盘表示、快速走子、价值评估、MCTS

AlphaGoZero 论文(Mastering the Game of Go without Human Knowledge)读着像数学推导,但落到工程上其实只有四个大模块。第一是棋盘状态表示,决定了 C++ 引擎怎么存盘、怎么判断气和提子;第二是神经网络,输入最近 8 步的黑白棋和历史状态,输出落子概率分布和当前胜率;第三是蒙特卡洛树搜索(MCTS),用神经网络指导树搜索,而不是像 AlphaGo Fan 那样依赖快速走子策略;第四是自我对弈数据管道,把一盘棋的每一步都存成训练样本。

这里有个容易被标题误导的点:大多数下载包里所谓的「AlphaGoZero」,其实只是 AlphaGo Zero 的简化实现,因为原始论文用 40 个残差块、每步模拟 1600 次,工程级别全量复现需要至少几十张 TPU。个人或小团队能跑的是「MiniZero」架构——把棋盘从 19 路缩到 9 路甚至 7 路,残差块压到 5~10 个,MCTS 模拟次数降到 100~400。但核心算法完全一致,棋力上限取决于你愿意跑多久。

2.2 为什么用 Python 做训练、C++ 做对弈:语言分工和进程边界

只要动手写过一遍,你就会明白 C++ 和 Python 的组合是性价比最高的选择,不是炫技。神经网络的训练过程本质是大量矩阵运算,这部分由 PyTorch/TensorFlow 的 C++ 后端完成,Python 只是控制层,性能损失可以忽略。而 MCTS 的自对弈展开,每一局都要进行成千上万次搜索节点的创建、UCB 值计算、网络推理,纯 Python 写会慢 20 倍以上。

我一般会这样切分进程边界:C++ 程序负责棋盘逻辑、MCTS 树搜索、调用神经网络做批量推理,并把生成的自我对弈样本写成本地文件;Python 脚本负责定义网络结构、加载 C++ 生成的样本做训练、把更新后的权重导出成 C++ 能读的格式。这样两个进程之间不需要频繁通信,只在训练轮次交替时交换一次数据。如果你看到某些开源项目用 C++ 调 libtorch 直接训练,那是另一种极端,代码维护成本很高,不适合起步。

2.3 数据流设计:自我对弈生成样本、训练、更新模型的完整回路

闭环数据流是整篇文章最值得先画清楚的东西。我把它拆成四步。第一步,C++ 引擎用当前模型参数跑 N 局自我对弈,每步落子前记录当前状态序列、MCTS 访问次数统计得到的落子概率、最终胜负结果。第二步,把样本按「棋局」为单位存储,落盘成二进制或 JSON 格式,注意每一手都带同一个最终胜负标签。第三步,Python 训练端读取缓冲区里的全部样本,随机打乱后训练若干批次,更新网络权重。第四步,导出权重到模型文件,供 C++ 端下次加载,循环往复。

这一步最容易出错的是样本的时间一致性:AlphaGoZero 要求训练数据永远来自「当前最新的模型」的自我对弈,而不是历史模型。所以整个流程必须串行迭代——先自对弈,再训练,更新模型后重新自对弈。如果你图省事用老模型生成的海量样本反复训练,棋力会卡在某个局部最优,后面避坑章节会细说。

3. 在 C++ 中实现围棋规则和高效棋形表示:从棋盘到气与死活

3.1 紧凑棋盘表示:用位棋盘还是二维数组

围棋棋盘只有 19 条线交叉出的 361 个点,理论上用二维数组是直觉选择,但 C++ 自对弈引擎每秒钟要创建几十万个节点,每个节点都要复制棋盘状态,这时候二维数组动态分配的开销就很致命。更常见的做法是「点编号 + 三色数组」:把棋盘拉平成一维数组,0 表示空点,1 表示黑子,2 表示白子。棋盘状态用std::array<int8_t, 361>存储,配合一组全局坐标映射表处理上下左右邻居。

如果你追求极致压缩,可以用位棋盘,黑棋和白棋各用一个 361 位的 bitset。但位棋盘在提子时要位运算计算连通块和边界气,调试难度陡增。我的建议是:第一版先用一维int8_t数组,保证逻辑正确;等对弈性能成为瓶颈后,再用位棋盘替换。标题里既然叫 C++,就说明你可以放心地在这里花时间做内存池和状态复用,这是 Python 实现做不到的优化空间。

3.2 落子与提子:核心函数实现与参数说明

围棋规则实现的核心是「落子 — 找气 — 提子 — 判断禁着」四步循环。下面给出一个精简单文件可以跑通的最小 C++ 逻辑,去掉文件名也足够看清楚算法主干:

// 棋盘大小为 N*N,用一维的 0/1/2 存储 // 记录每个点的"气"不缓存,每次提子时暴力计算连通块 struct GoBoard { static constexpr int N = 9; // 训练用小棋盘 std::array<int8_t, N * N> grid{}; // 0空 1黑 2白 std::array<int8_t, N * N> visited{}; // 连通块标记 int ko_point = -1; // 打劫禁止点,-1表示无 int idx(int x, int y) const { return x + y * N; } bool in_bounds(int x, int y) const { return x >= 0 && x < N && y >= 0 && y < N; } // 统计连通块的气:从落子点淹没同色棋,返回气数量 int count_liberties(int x, int y) { int color = grid[idx(x, y)]; int liberties = 0; std::vector<int> stack = {idx(x, y)}; visited[idx(x, y)] = 1; std::array<int, 4> dx = {1, -1, 0, 0}; std::array<int, 4> dy = {0, 0, 1, -1}; while (!stack.empty()) { int cur = stack.back(); stack.pop_back(); int cx = cur % N, cy = cur / N; for (int d = 0; d < 4; d++) { int nx = cx + dx[d], ny = cy + dy[d]; if (!in_bounds(nx, ny)) continue; int ni = idx(nx, ny); if (grid[ni] == 0) liberties++; else if (grid[ni] == color && !visited[ni]) { visited[ni] = 1; stack.push_back(ni); } } } return liberties; } void clear_visited() { visited.fill(0); } // 落子并提子,返回是否合法;is_ko_only用于打劫判断 bool place_stone(int x, int y, int color) { int i = idx(x, y); if (grid[i] != 0) return false; if (i == ko_point) return false; // 直接禁止落在劫点 grid[i] = color; // 检查相邻四气的对手棋是否只剩一口气,是则提 std::array<int, 4> dx = {1, -1, 0, 0}; std::array<int, 4> dy = {0, 0, 1, -1}; bool captured = false; int captured_count = 0; int last_captured[2] = {-1, -1}; for (int d = 0; d < 4; d++) { int nx = x + dx[d], ny = y + dy[d]; if (!in_bounds(nx, ny)) continue; int opp = grid[idx(nx, ny)]; if (opp != 0 && opp != color) { int libs = count_liberties(nx, ny); if (libs == 0) { // 无气,提掉整个连通块 // 提取该连通块所有棋子并清空 captured = true; clear_visited(); // 复用visited做提子标记 // 这里省略提取函数,实际用栈收集整个块 // 并把位置记入last_captured } } } // 落子后检查己方气,若为0且没有提走对手,则自杀非法 if (!captured) { int self_libs = count_liberties(x, y); clear_visited(); if (self_libs == 0) { grid[i] = 0; // 悔子,恢复棋盘 return false; } } return true; } };

这段代码把「气」的计算当作核心操作,每次提子暴力遍历连通块。参数说明里最值得修改的是N,9 路棋谱训练速度快、适合验证网络能否收敛;改成 19 后搜索空间扩大约 4.5 倍,同样的训练轮数下棋力会明显下降。ko_point的实现只解决了最简单的单劫禁着,完整打劫需要记录上一步整个棋盘的哈希,用 Zobrist 哈希判断局面重复,后面避坑章节会展开。

3.3 虚气与全局禁着(打劫)的正确判定:3 行规则还是完整 ko

很多开源 zip 包里的 C++ 引擎只在place_stone里用「禁止提劫的那一个点」来防空劫,这在大部分对局里够用,但遇到双劫、循环劫就会翻车。国际规则里正式禁着是「全局同形禁止」——即不允许落下导致棋盘状态与历史完全相同的子。工程实现常用 Zobrist 哈希:给每个点的每种子状态分配一个 64 位随机数,棋盘哈希值是所有点上随机数的异或和。落子时增量更新哈希,每步落子后把哈希压入列表,如果某步落子后哈希在历史中出现过,则判为禁着。

这个方案的坑在于 Zobrist 随机表必须稳定生成,否则同样的棋局在不同进程中哈希不一致。我的做法是在 C++ 侧用std::mt19937_64和固定种子生成 Zobrist 表,并把这个表导出到文件,让 Python 端的规则校验(如果有)读取同一张表。如果你只需要训练网络,其实可以先用简单的ko_point方案跑通流程,等棋力进入瓶颈期再更换为完整 ko,因为简单的劫争错误会让 MCTS 产生幻觉——它以为某一步能提子,但实际被禁,整个搜索概率分布就会偏。

4. 用 Python 复现 MCTS 与神经网络:最小可跑的训练回路

4.1 蒙特卡洛树搜索的四步:选择、扩展、模拟、回传

AlphaGoZero 的 MCTS 没有随机模拟,它的「模拟」指的是用神经网络直接预测该局面的价值,这和传统 MCTS 的 Monte Carlo rollout 完全不同。每一步搜索从根节点出发,按照 UCB 公式选择子节点,直到叶子节点;然后用神经网络的策略头和价值头同时给出先验概率和局面胜率,并逐层回传进行均值更新。

UCB 公式是核心,AlphaGoZero 使用PUCT = Q(s,a) + c_puct * P(s,a) * sqrt(sum_N(s)) / (1 + N(s,a))。Q是当前节点的平均价值,P是神经网络输出的先验概率,N是访问次数,c_puct是探索常数,论文默认设为 4 左右。python 端保持数学原型,C++ 端用同样公式展开树——但注意浮点数误差会导致两个端搜索树不完全一致,所以自对弈生成数据时必须以 C++ 端为准,Python 端只做离线训练。

4.2 把卷积残差网络压成 200 行:输入特征 17 层、输出策略和价值

AlphaGoZero 的输入是17 × N × N的特征平面:最近 8 步黑棋、最近 8 步白棋(当前视角),加上当前玩家是否黑棋的常量平面。网络主干是残差卷积块,每个块由两个 3×3 卷积层和批归一化、ReLU 组成。输出分为两个头:策略头输出361+1维概率(含 pass 动作),价值头输出一个tanh标量表示当前玩家胜率。

PyTorch 实现核心代码如下:

import torch import torch.nn as nn import torch.nn.functional as F class ResidualBlock(nn.Module): def __init__(self, channels=256): super().__init__() self.conv1 = nn.Conv2d(channels, channels, 3, padding=1, bias=False) self.bn1 = nn.BatchNorm2d(channels) self.conv2 = nn.Conv2d(channels, channels, 3, padding=1, bias=False) self.bn2 = nn.BatchNorm2d(channels) def forward(self, x): residual = x x = F.relu(self.bn1(self.conv1(x))) x = self.bn2(self.conv2(x)) x = F.relu(x + residual) # 残差连接一定要用 relu 包住加法 return x class AlphaZeroNet(nn.Module): def __init__(self, board_n=9, blocks=6, channels=128): super().__init__() self.board_n = board_n self.conv_input = nn.Conv2d(17, channels, 3, padding=1, bias=False) self.bn_input = nn.BatchNorm2d(channels) self.blocks = nn.Sequential(*[ResidualBlock(channels) for _ in range(blocks)]) # 策略头 self.policy_conv = nn.Conv2d(channels, 4, 1, bias=False) self.policy_bn = nn.BatchNorm2d(4) self.policy_fc = nn.Linear(4 * board_n * board_n, board_n * board_n + 1) # 价值头 self.value_conv = nn.Conv2d(channels, 2, 1, bias=False) self.value_bn = nn.BatchNorm2d(2) self.value_fc1 = nn.Linear(2 * board_n * board_n, 128) self.value_fc2 = nn.Linear(128, 1) def forward(self, x): x = F.relu(self.bn_input(self.conv_input(x))) x = self.blocks(x) # 策略输出 p = F.relu(self.policy_bn(self.policy_conv(x))) p = p.view(p.size(0), -1) p = self.policy_fc(p) # 未经过 softmax,训练时用 CE Loss p = F.log_softmax(p, dim=1) # 价值输出 v = F.relu(self.value_bn(self.value_conv(x))) v = v.view(v.size(0), -1) v = F.relu(self.value_fc1(v)) v = torch.tanh(self.value_fc2(v)) return p, v

这里几个参数直接影响训练速度和棋力。channels=128对 9 路棋盘足够,如果直接上 19 路,建议加到 192 或 256;blocks=6是入门配置,训练一天能看到棋力增长,想对标论文至少 20 个块,但没 GPU 的话会非常痛苦。策略头的输出维度是board_n * board_n + 1,多出的 1 是 pass 动作;实际多数简化围棋实现没有 pass,那你就要在样本生成时保证不会出现无点可落的情况,否则这个维度就要去掉。这里的关键是log_softmax配合NLLLoss,不要用softmax + CrossEntropyLoss,因为两种写法对 NaN 的容忍度不同。

4.3 自我对弈样本生成:如何用温度参数控制探索

自我对弈时,每一步并不是直接选 MCTS 访问次数最多的落点,而是根据访问次数分布采样。AlphaGoZero 引入温度参数tau:棋局前 10 手温度较高,采样时概率分布平坦,偏向探索;之后温度降到接近 0,直接取访问次数最多的点。这个设计是为了让早期对局产生更多样化的棋谱,避免网络过早收敛到某一种开局。

我的 C++/Python 协作实现在这里要注意接口一致性。C++ 引擎输出每步的访问次数数组,Python 端可以读这个数组自己算温度采样,但我倾向于直接在 C++ 端完成温度采样并输出最终落子坐标,这样自对弈进程和训练进程解耦更干净。如果想把采样逻辑放在 Python 端方便试验不同温度曲线,对应的代价是要把整个棋盘状态发给 Python,数据量大了之后进程间通信会明显变慢,所以低温部分的采样尽量放 C++。

5. 训练稳定性和性能踩坑:5 条让我翻车的血泪记录

5.1 棋力一直在 30k 徘徊:归一化和残差连接的问题

现象是训练了一两万步,胜率一直维持在 50% 上下——跟随机落子差不多,策略输出的熵也降不下来。我之前有段时间怀疑是 MCTS 写错了,排查半天,最后发现神经网络输入没有做统一缩放。AlphaGoZero 的特征平面值是 0/1 的布尔标记,但对 CNN 来说 0/1 分布和 BatchNorm 配合没问题。关键在于价值头的目标值:自我对弈最终胜负是+1/-1,但如果是「贴目」规则,胜负可能接近+0.5/-0.5,直接用tanh输出会使网络大部分时间在饱和区,梯度消失。解决方法是训练时把胜负标签除以一个缩放因子(比如 2),让网络输出范围缩小到 0.5 左右。

5.2 打劫实现错误导致"永远提不掉":ko 判定的状态边界

现象是 C++ 自对弈里出现循环提劫,一个局面反复出现,MCTS 的搜索树被无限同一状态填充。原因在于我用ko_point只禁了单劫,没有处理「多个劫并存」和「劫争期间提其他子的情况」。最简单的解决是记录最近 8 步的 Zobrist 哈希,落子后如果哈希在历史中出现过,则拒绝这步。注意检查历史哈希时只回溯当前对局,而不是跨对局。这个坑在 9 路棋盘上尤其常见,因为小棋盘更容易出现劫争,如果开局棋谱数据里大量包含非法打劫,训练出的策略头会学习到「提劫后被立刻提回」的错误应对。

5.3 自对弈样本严重相关:经验回放缓冲区的 shuffle 和优先级

现象是训练 loss 抖动剧烈,甚至一度发散。原因是我的 C++ 自对弈程序按棋局顺序存储样本,训练时直接按顺序读,导致一个 batch 里的几百条样本全部来自同一局棋的不同步,高度相关。解决方法是训练前对整个样本池做随机打乱,并确保不同棋局的数据交错出现在同一个 batch。更进一步的教训是 AlphaGoZero 的缓冲区大小要有限制——它总是丢弃最旧的数据,只保留最近 50 万局。如果全量保留历史棋局,模型会被旧策略的棋谱拉回去。这也是下载包常见的坑,很多人训练很久发现棋力不涨,其实是样本池太大且没有按棋局时间剔除。

5.4 C++ 接口被 Python 拖慢:进程通信改为批量推理

现象是 C++ 自对弈引擎的模拟速度只有每秒几十局,远低于预期。用 profiler 查发现瓶颈不在 MCTS 循环,而在每调用一次神经网络就要socket/subprocess把棋盘数据发到 Python 再等结果,往返时间占据 90%。解决策略是批量推理:C++ 端自己维护 MCTS 队列,攒够 64 或 128 个待评估局面后一次性通过 ONNX Runtime 或 TensorRT 加载模型做批量推理,再把结果回填到树节点。这一步能把吞吐量提升一个数量级。事实上如果你下载的 zip 包里的 C++ 程序还在用单发推理,基本可以断定它跑不出论文效果。

5.5 随机数种子导致复现失败:C++ 和 Python 的随机数生成器差异

现象是同一次训练两次运行结果完全不同,甚至第一次能收敛第二次发散。原因不是模型结构问题,而是 C++ 自对弈用的std::mt19937和 Python 端的random模块如果不显式指定种子,每次种子不同。更隐蔽的是 Zobrist 哈希表的随机数:如果每次启动 C++ 引擎重新生成随机表,同一局棋在重启后哈希完全不同,基于哈希的打劫判定自然崩掉。我的做法是三条:C++ 端默认std::mt19937_64(20240220)固定种子;Python 训练端torch.manual_seed固定;Zobrist 表在初始化时用固定种子生成后落盘,后续所有进程都从文件加载。这样即使模型权重有随机初始化,自对弈数据本身可复现,排查问题时至少能区分是优化问题还是代码问题。

6. 从跑通到棋力进阶:验证训练的 3 个手段和让模型涨棋的细节

验证一个 AlphaGoZero 实现是否正常,不能只看训练 loss。我的习惯是固定三个指标。第一,策略头对「标准定式棋谱」的预测准确率——收集 100 局人类 9 路棋谱,看模型给出的 top-1 落子是否在人类落子的附近;如果准确率长期低于 20%,说明网络还没有学到棋形概念,优先怀疑输入特征和样本生成逻辑。第二,和随机走子引擎对战 100 局,胜率应当从 50% 起步,随训练推进接近 100%;如果连随机走子都打不过,说明 MCTS 完全失效。第三,也是最重要的,观察 MCTS 搜索后策略输出的熵:从第 1 部的 logsoftmax 熵值从大约 5.5 逐渐降低到 2 以下,说明网络开始对自己的选择有信心,否则就是温度参数没降下来或者 MCTS 模拟次数太少。

进阶阶段我一般做两件事。一件是把 MCTS 模拟次数从 100 提升到 400,同时把 c_puct 从 4 降到 2,让搜索更依赖网络先验,这个改变能让棋力在同样训练步数下有明显上升。另一件是异步自我对弈——在 C++ 端开 8 个线程各自跑对弈,每线程独立加载同一份模型权重,收集样本写入一个共享的无锁队列;前提是每个线程的随机数种子不同,否则只会收到 8 份完全相同的棋谱。很多开源包喜欢用多进程 Python 自对弈,但因为 GIL 的存在,CPU 推理的瓶颈依然在 Python 侧,效果不如 C++ 多线程。

最后说一个我的习惯:永远保留每 1000 步训练后的权重快照,复盘棋局时用旧权重和新权重对战。这个「权重锦标赛」比看 loss 曲线真实得多,也是我判断训练是否卡死的最直接手段。AlphaGoZero 的复现本质上是一场工程马拉松,网络结构抄得快,但样本管道、C++ 引擎和训练的配合要靠自己反复打磨。代码跑通只是开始,希望这些细节能帮你在调参和排错上少走几个弯路。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 2:02:28

做网站公司哪家公司靠谱?看懂建站报价避坑指南

做网站公司哪家公司靠谱?看懂建站报价避坑指南 找建站公司最怕什么?不是技术不行,而是报价像黑箱,签了合同才发现被坑了高价。很多人拿着“5000元全包”的诱饵就签了字,最后加个SSL证书都要再掏两千,改个页面颜色又算“定制开发”收费三千。这种“低门槛进门,高成本出局”的套路,在行业里太常见了。…

作者头像 李华
网站建设 2026/9/28 2:02:18

STM32G4三相电流实时监控:DMA+VOFA+零门槛波形可视化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 2:02:05

四足机器人MPC控制实战:OCS2与Pinocchio完整搭建指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 2:02:07

3招用免费工具搞定wordpress撰写邮箱避坑指南

3招用免费工具搞定wordpress撰写邮箱避坑指南 找建站公司最怕什么?不是代码写不出来,而是被坑高价。很多站长为了省几百块,结果花了上万块买教训。其实,像 wordpress撰写邮箱 这种基础配置,根本不需要依赖外包。利用 免费工具 ,你完全能自己搞定,还能省下大笔预算。…

作者头像 李华
网站建设 2026/9/28 2:02:03

高通平台启动流程解析:从PBL到UEFI的完整链路与故障排查

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 2:02:01

杭州网站建设文章必看:防黑客挂马的5个注意事项

杭州网站建设文章必看:防黑客挂马的5个注意事项 昨天凌晨三点,杭州一家做跨境电商的老板给我打电话,声音都在抖。他说刚打开公司官网,发现首页被改成了赌博广告,后台多了几十个陌生的管理员账号。更吓人的是,服务器日志里显示,他的数据库在半夜两点到四点之间,被疯狂读取了三次,每次持续十几分钟。这就是典型的网…

作者头像 李华