Minecraft 工业2 实验版核反应堆计算 强化学习模块训练路径

最近在玩Minecraft IC2 Classic,但是对于摆核反应堆总是感觉不是很得心应手,不管怎么摆效率都很低,为了解决这个问题,所以我写了一个强化学习的模块,让神经网络自己去学习如何摆弄这个网络。

不过看了下,IC2 Classic 的核反应堆因为似乎不涉及中子流,所以任务是比较简单的,为了节目效果,我准备研究一下IC2 experiment版的核电站,这个玩起来更有趣,学习的深度也更深。

任务简单分为三步:

  1. 明确任务目标和行为
  2. 搭建网络
  3. 训练

一、任务目标和行为指南

这一步往往是比较重要的,因为这一步决定了AI到底要学什么,以及怎么学。

1.1 问题定义

IC2E的核反应堆设计本质上是一个组合优化问题。你有一个9×6的网格,54个位置,每个位置可以放18种不同的组件(包括空槽位)。理论上有18^54种可能的配置,这个数字大到宇宙中的原子都数不过来。

但问题是,这些配置里99.99%都是垃圾——要么发电量低得可怜,要么直接爆炸。我们要找的是那0.01%既能高效发电,又不会炸的设计。

1.2 核反应堆的物理机制

在开始训练之前,得先搞清楚IC2E核反应堆到底是怎么工作的。不然AI学出来的东西可能完全不符合物理规律。

核脉冲机制

  • 燃料棒工作时会向四周发射核脉冲
  • 相邻的燃料棒接收到核脉冲后,发电量会成倍增加
  • 中子反射板可以把核脉冲反射回去,相当于"虚拟"的燃料棒

举个例子,一个单铀棒(U):

  • 单独放置:发电5 EU/t
  • 旁边有1个燃料棒:发电10 EU/t
  • 旁边有2个燃料棒:发电15 EU/t
  • 旁边有4个燃料棒:发电25 EU/t

所以燃料棒越密集,发电效率越高。但问题来了——

热量产生机制
燃料棒产生的热量跟相邻的燃料棒/反射板数量有关,公式是:

热量 = 倍数 × (n+1) × (n+2)

其中n是相邻的燃料棒或反射板数量(0-4)。

这个公式很狠,是二次增长的。比如单铀棒(倍数=2):

  • n=0(孤立):2×1×2 = 4 HU/tick
  • n=1(1个邻居):2×2×3 = 12 HU/tick
  • n=2(2个邻居):2×3×4 = 24 HU/tick
  • n=4(4个邻居):2×5×6 = 60 HU/tick

看到没?发电量是线性增长(5→25),但热量是二次增长(4→60)。这就是核反应堆设计的核心矛盾:你想要高功率,就得承受高热量

散热系统
热量如果散不出去,反应堆温度就会一路飙升,到10000 HU就爆炸。所以必须有足够的散热系统:

  • 散热片(H):自身散热6 HU/tick
  • 反应堆散热片(R):从堆温吸热5 HU/tick,然后自己散热5 HU/tick
  • 高级散热片(A):自身散热12 HU/tick
  • 超频散热片(O):自身散热20 HU/tick

还有热交换器,可以在组件之间转移热量,把热量从燃料棒转移到散热片上。

1.3 强化学习的任务建模

搞清楚物理机制后,就可以把这个问题建模成强化学习任务了。

状态空间(State Space)
AI需要"看到"当前反应堆的状态。我用了一个18×9×6的三维张量:

  • 18个通道,每个通道对应一种组件类型
  • 9×6是反应堆的网格大小
  • 用one-hot编码:如果某个位置放了某种组件,对应通道就是1,否则是0

这种表示方式的好处是,神经网络可以直接处理,而且能保留空间信息。

动作空间(Action Space)
这里我踩了个大坑。

最开始的设计(V1版本)是让AI同时选择三个东西:

  • 行号(0-8)
  • 列号(0-5)
  • 组件类型(0-17)

用的是MultiDiscrete([9, 6, 18]),总共972种可能的动作。

听起来很合理对吧?但实际训练时发现了严重的问题:

无效动作问题
随着反应堆逐渐被填满,越来越多的位置已经被占用了。如果AI选择了一个已经有组件的位置,这个动作就是无效的。

问题有多严重?我统计了一下:

  • 刚开始:0%无效动作
  • 填了一半:50%无效动作
  • 快填满时:83%+无效动作

这意味着AI大部分时间都在做无效动作,得到负反馈。结果AI学到的策略就是"一直输出同一个动作",因为这样至少不会被惩罚太多。

更糟糕的是,训练时因为有随机探索,看起来还正常;但评估时用确定性策略,AI就完全不会了,直接崩盘。

V2版本的解决方案
既然位置选择这么麻烦,干脆不让AI选了。改成:

  • AI只选择组件类型(Discrete(18))
  • 位置按从左到右、从上到下的顺序自动填充

这样就完全避免了无效动作问题。每个动作都是有效的,学习信号清晰,训练稳定。

这个改动看起来简单,但效果差别巨大。V1版本训练20万步还是一团糟,V2版本5万步就能看到明显的学习效果。

奖励函数(Reward Function)
奖励函数决定了AI的优化目标。我的设计思路是:

  1. 主要目标:发电量

    • 奖励 = 平均功率 × 1.5
    • 比如平均300 EU/t,就得450分
  2. 安全约束:不能爆炸

    • 爆炸惩罚:-500
    • 但如果爆炸前发了不少电,也给点安慰分(平均功率×0.2)
    • 这样鼓励AI探索高功率设计,而不是一味保守
  3. 温度控制

    • 堆温超过90%:严重惩罚(-200×超出比例)
    • 堆温超过70%:轻度惩罚(-50×超出比例)
    • 这样AI会学会控制温度
  4. 稳定性奖励

    • 完整跑完1000 ticks:+50
    • 鼓励AI设计能长期运行的反应堆
  5. 中间步骤的小奖励

    • 放燃料棒:+0.1
    • 放散热片:+0.05
    • 放其他组件:+0.02
    • 这样AI在填充过程中也有正反馈,不会完全迷失

这些数字都是调出来的。比如爆炸惩罚最开始是-1000,发现AI太保守了,功率上不去,就改成-500。温度惩罚的阈值也试了好几个值,最后定在70%和90%。

二、搭建网络

2.1 环境实现

强化学习的环境需要实现Gymnasium的接口。核心是三个方法:

reset():重置环境

def reset(self):
    self.reactor = Reactor(9, 6, max_hull_heat=10000)
    self.current_layout = np.zeros((9, 6), dtype=np.int32)
    self.current_position = 0  # 当前要填充的位置
    return self._get_observation(), self._get_info()

step(action):执行一个动作

def step(self, action):
    # action就是组件类型索引(0-17)
    component_idx = action

    # 计算当前位置(自动填充)
    row = self.current_position // 6
    col = self.current_position % 6

    # 放置组件
    component_code = self.AVAILABLE_COMPONENTS[component_idx]
    self.current_layout[row, col] = component_idx
    self.current_position += 1

    # 如果还没填满,给个小奖励
    if self.current_position < 54:
        reward = self._calculate_intermediate_reward(component_code)
        terminated = False
    else:
        # 填满了,运行模拟,计算最终奖励
        reward, terminated = self._evaluate_reactor()

    return observation, reward, terminated, truncated, info

_evaluate_reactor():评估反应堆设计

def _evaluate_reactor(self):
    # 从布局重建反应堆
    self.reactor.load_layout(layout_codes)

    # 运行1000个tick的模拟
    total_power = 0.0
    max_heat = 0.0
    exploded = False

    for tick in range(1000):
        result = self.reactor.simulate_tick()
        total_power += result["power"]
        max_heat = max(max_heat, result["hull_heat"])

        if result["exploded"]:
            exploded = True
            break

    # 计算平均功率和奖励
    avg_power = total_power / (tick + 1)
    reward = self._calculate_reward(avg_power, max_heat, exploded, tick+1)

    return reward, exploded

2.2 反应堆模拟器

环境的核心是反应堆模拟器,它要准确模拟IC2E的物理机制。

模拟流程(每个tick):

  1. 燃料棒产生热量和电力

    • 计算接收到的核脉冲数
    • 计算发电量:5 × (base_output + received_pulses)
    • 计算产热量:multiplier × (n+1) × (n+2)
  2. 热量分配

    • 燃料棒产生的热量均分给周围可储热组件
    • 如果周围没有组件,热量传给反应堆本体
    • 如果组件满了,溢出的热量也传给反应堆本体
  3. 热交换器工作

    • 在组件之间转移热量(高温→低温)
    • 与反应堆本体交换热量
  4. 散热片工作

    • 自身散热
    • 从反应堆吸热并散发
    • 从相邻组件吸热并散发
  5. 检查状态

    • 组件是否过热损坏
    • 反应堆是否爆炸(≥10000 HU)

这个模拟器我写了大概1000行代码,实现了IC2E的所有核心机制。测试了几个已知的设计,模拟结果和游戏里基本一致。

2.3 神经网络结构

用的是Stable-Baselines3的PPO算法,默认的MlpPolicy。

网络结构:

  • 输入层:972维(18×9×6展平)
  • 隐藏层1:64个神经元,ReLU激活
  • 隐藏层2:64个神经元,ReLU激活
  • 输出层
    • Policy head:18维(每个组件的概率分布)
    • Value head:1维(状态价值估计)

为什么不用CNN?因为反应堆的空间结构不像图像那么重要。燃料棒在左上角和右下角,对整体性能的影响是一样的。MLP够用了,而且训练更快。

2.4 算法选择

试过几个算法:

DQN

  • 优点:经典,好理解
  • 缺点:不太稳定,容易崩
  • 结果:训练了10万步,功率一直在50左右徘徊

A2C

  • 优点:比DQN稳定
  • 缺点:样本效率低,需要很多步才能学会
  • 结果:20万步能到200 EU/t,但还不够好

PPO

  • 优点:稳定,样本效率高,对超参数不敏感
  • 缺点:没啥明显缺点
  • 结果:10万步就能到300 EU/t,20万步能到400+

最后选了PPO,主要是因为稳定。强化学习本来就不稳定,能用稳定的算法就用稳定的。

三、训练

3.1 训练配置

超参数

learning_rate = 3e-4      # 学习率
n_steps = 2048            # 每次更新前采集的步数
batch_size = 64           # 批大小
n_epochs = 10             # 每批数据训练的轮数
gamma = 0.99              # 折扣因子
gae_lambda = 0.95         # GAE参数
clip_range = 0.2          # PPO裁剪范围

这些基本都是Stable-Baselines3的默认值,我没怎么调。PPO的好处就是默认参数就很好用。

并行环境
用了4个并行环境同时采集数据。这样训练快,而且能增加数据多样性。

如果CPU核心多,可以开到8个甚至16个。但要注意内存占用,每个环境都要跑完整的反应堆模拟。

3.2 训练过程

运行命令:

python rl_train.py --timesteps 200000 --n-envs 4

训练时的输出:

IC2 核反应堆强化学习训练
============================================================
算法: PPO
策略: MlpPolicy
总步数: 200000
并行环境: 4
学习率: 0.0003
动作空间: Discrete(18) - 只选择组件,无无效动作
模型名称: PPO_20260330_143022
============================================================

Episode 完成: 平均功率=120.00 EU/t, 最大堆温=8500.00, 爆炸=否
Episode 完成: 平均功率=85.00 EU/t, 最大堆温=12000.00, 爆炸=是
Episode 完成: 平均功率=200.00 EU/t, 最大堆温=6500.00, 爆炸=否
Episode 完成: 平均功率=280.00 EU/t, 最大堆温=5200.00, 爆炸=否
...

可以看到,刚开始AI还在瞎搞,有时候爆炸,有时候功率很低。但慢慢地,功率在涨,爆炸率在降。

训练曲线
用TensorBoard可以看到训练曲线:

tensorboard --logdir rl_logs

关键指标:

  • ep_rew_mean:平均奖励,应该持续上升
  • ep_len_mean:平均episode长度,应该稳定在54(填满网格)
  • value_loss:价值函数损失,应该逐渐下降
  • policy_loss:策略损失,会有波动,但整体趋势下降

如果曲线一直平着不动,说明没学到东西,可能需要调整奖励函数或增加训练步数。

3.3 评估结果

训练完后,用最佳模型评估:

python evaluate_model.py rl_models/PPO_xxx/best/best_model.zip --n-episodes 20

我的结果(20万步训练):

评估统计:
  平均奖励: 425.30 ± 35.20
  平均功率: 360.80 ± 45.60 EU/t
  最大功率: 480.00 EU/t
  最小功率: 280.00 EU/t
  爆炸率: 5.00%
  平均堆温: 5500.00 ± 1200.00

这个结果还不错:

  • 平均功率360 EU/t,已经比手工设计的很多方案好了
  • 爆炸率只有5%,说明AI学会了控制温度
  • 温度控制在5500左右,很安全

如果训练50万步,能到400-500 EU/t,爆炸率降到2-3%。

3.4 AI设计的反应堆长什么样

我看了几个AI设计的反应堆,发现了一些有趣的模式:

模式1:对称布局
AI经常设计出对称的布局,比如:

E  E  E  E  E  E
E  U  H  U  H  E
E  H  O  O  H  E
E  U  H  U  H  E
E  H  O  O  H  E
E  E  E  E  E  E
...

燃料棒和散热片交替排列,很工整。

模式2:核心+外围
把燃料棒集中在中间,散热片围在外面:

E  E  E  E  E  E
E  H  H  H  H  E
E  H  U  U  H  E
E  H  U  U  H  E
E  H  H  H  H  E
E  O  O  O  O  E
...

这样热量集中,但散热也集中。

模式3:分散布局
把燃料棒分散开,避免过热:

E  U  E  U  E  E
E  H  E  H  E  E
E  E  E  E  E  E
E  U  E  U  E  E
E  H  E  H  E  E
...

功率不高,但很安全。

有意思的是,AI会根据奖励函数的权重,自动调整策略。如果我把爆炸惩罚调高,AI就会设计更保守的方案;如果把功率权重调高,AI就会冒险设计高功率方案。

3.5 遇到的问题和解决方案

问题1:训练不收敛

  • 症状:训练了10万步,功率还是很低,没有上升趋势
  • 原因:奖励函数设计有问题,或者动作空间有问题
  • 解决:检查奖励函数,确保有正反馈;简化动作空间(V1→V2)

问题2:评估时表现很差

  • 症状:训练时看起来正常,评估时功率很低或者总是爆炸
  • 原因:训练时的随机探索掩盖了问题,评估时用确定性策略就暴露了
  • 解决:简化动作空间,避免无效动作

问题3:训练很慢

  • 症状:每秒只能跑几十步,训练20万步要好几个小时
  • 原因:反应堆模拟太慢,每个tick都要计算很多东西
  • 解决:优化模拟代码;增加并行环境数;减少模拟tick数(但可能影响效果)

问题4:模型过拟合

  • 症状:训练集表现很好,但换个随机种子就不行了
  • 原因:训练数据不够多样
  • 解决:增加并行环境数;增加训练步数;调整探索率

四、总结和展望

4.1 项目总结

这个项目最大的收获是:环境设计比算法更重要

V1版本用了复杂的动作空间,试了各种算法和超参数,怎么调都不行。V2版本简化了动作空间,用默认参数就能训练出不错的模型。

所以如果你也在做强化学习项目,遇到训练不收敛的问题,先检查环境设计:

  • 动作空间是否合理?有没有大量无效动作?
  • 奖励函数是否清晰?AI能不能得到有效的学习信号?
  • 状态表示是否充分?AI能不能"看到"足够的信息?

算法和超参数反而是次要的。PPO用默认参数就很好用。

4.2 性能对比

和手工设计的反应堆比:

  • 手工设计:需要反复试错,可能要调试几十次才能找到好方案
  • AI设计:训练一次,可以生成无数个方案,而且性能不错

和暴力搜索比:

  • 暴力搜索:搜索空间太大(18^54),根本搜不完
  • 强化学习:智能探索,只需要几十万步就能找到好方案

4.3 后续改进方向

现在的版本能用,但还有改进空间:

1. 可视化AI设计
现在只能看到动作序列和数字,如果能直接画出反应堆布局图就更直观了。可以用matplotlib画个热力图,显示每个位置的组件和温度。

2. 导出配置文件
把AI设计导出成YAML文件,可以直接用在游戏里。这样就能在游戏中验证AI的设计是否真的有效。

3. 多目标优化
现在只优化功率和安全性,还可以考虑:

  • 成本:不同组件的材料成本不同
  • 耐久:燃料棒和反射板会损耗,需要定期更换
  • 启动时间:有些设计需要预热,有些可以立即满功率运行

可以用多目标强化学习算法,比如MORL,同时优化多个目标。

4. 迁移学习
现在训练的是9×6的反应堆,如果要设计6×6或者12×6的反应堆,需要重新训练。可以用迁移学习,把9×6的知识迁移到其他尺寸。

5. 课程学习
先让AI学简单的设计(比如只用单铀棒和散热片),再学复杂的设计(加入双联、四联燃料棒和热交换器)。这样学习曲线可能更平滑。

6. 人类反馈
可以让玩家评价AI的设计,把评价作为额外的奖励信号。这样AI可以学到一些难以量化的偏好,比如"布局要美观"、"要容易维护"等。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐