[RL]强化学习指导搭建IC2E核反应堆
Minecraft 工业2 实验版核反应堆计算 强化学习模块训练路径
最近在玩Minecraft IC2 Classic,但是对于摆核反应堆总是感觉不是很得心应手,不管怎么摆效率都很低,为了解决这个问题,所以我写了一个强化学习的模块,让神经网络自己去学习如何摆弄这个网络。
不过看了下,IC2 Classic 的核反应堆因为似乎不涉及中子流,所以任务是比较简单的,为了节目效果,我准备研究一下IC2 experiment版的核电站,这个玩起来更有趣,学习的深度也更深。
任务简单分为三步:
- 明确任务目标和行为
- 搭建网络
- 训练
一、任务目标和行为指南
这一步往往是比较重要的,因为这一步决定了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.5
- 比如平均300 EU/t,就得450分
-
安全约束:不能爆炸
- 爆炸惩罚:-500
- 但如果爆炸前发了不少电,也给点安慰分(平均功率×0.2)
- 这样鼓励AI探索高功率设计,而不是一味保守
-
温度控制
- 堆温超过90%:严重惩罚(-200×超出比例)
- 堆温超过70%:轻度惩罚(-50×超出比例)
- 这样AI会学会控制温度
-
稳定性奖励
- 完整跑完1000 ticks:+50
- 鼓励AI设计能长期运行的反应堆
-
中间步骤的小奖励
- 放燃料棒:+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):
-
燃料棒产生热量和电力
- 计算接收到的核脉冲数
- 计算发电量:5 × (base_output + received_pulses)
- 计算产热量:multiplier × (n+1) × (n+2)
-
热量分配
- 燃料棒产生的热量均分给周围可储热组件
- 如果周围没有组件,热量传给反应堆本体
- 如果组件满了,溢出的热量也传给反应堆本体
-
热交换器工作
- 在组件之间转移热量(高温→低温)
- 与反应堆本体交换热量
-
散热片工作
- 自身散热
- 从反应堆吸热并散发
- 从相邻组件吸热并散发
-
检查状态
- 组件是否过热损坏
- 反应堆是否爆炸(≥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可以学到一些难以量化的偏好,比如"布局要美观"、"要容易维护"等。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)