☰
三维在线装箱DQN实战:从状态编码到奖励函数的建模与避坑
2026/10/7 5:52:01 网站建设 项目流程

简介:基于DQN深度强化学习解决三维在线装箱问题的完整Python工程,面向物流智能优化、深度强化学习方向的在校学生、毕设开发者或算法研究者,针对车厢装载率提升这一典型场景提供了可运行的强化学习解决方案。压缩包共10个文件,包含5个Python源文件,分别覆盖模型训练、在线评估、数据生成、容器环境定义等核心模块;另有预训练模型权重(.pth)、README说明文档(.md)与装箱效果示意图(.png),模块划分清楚、文件用途一目了然,整体体积约5.64MB。目前已有104人学习下载。代码已经过实际运行验证,功能可靠,从三维车厢建模、箱子序列生成到DQN训练与在线装箱评估形成完整链路;配套文档说明了问题定义、运行方式与关键代码位置,能够帮助读者快速理解DQN在组合优化中的应用思路。遇到环境配置或调用问题时,可联系作者获得远程教学支持,进一步降低上手门槛。

1. 三维在线装箱上跑DQN:为什么几何问题被做成了强化学习

在线三维装箱问题是典型的序列决策:箱子逐个到达,每个箱子到达时必须在当前托盘状态上立刻决定放哪里、怎么旋转,不能回头调整。这个特性让经典的启发式搜索很吃亏——搜索需要全局信息,在线场景下信息永远不完整。把问题交给DQN这类深度强化学习,等于训练出一个“看一眼当前堆叠空间和箱子尺寸就出放置策略”的决策模型,装箱策略不再依赖人工规则模板,而是用奖励信号从仿真里直接长出来。适合正在做仓储调度、集装箱配载、机械臂码垛仿真的人,也适合想入行深度强化学习和组合优化交叉方向的研究者。这里从建模到训练踩坑,给一条能在Python里复现的完整路径。

2. 装箱问题怎么“喂”给DQN:状态、动作、奖励的三件套建模

2.1 为什么把容器切成立方体网格:离散化是给神经网络“能理解”的关键

在线三维装箱的难点在于连续空间里候选位置是无限的。DQN输出的是离散Q值,神经网络没法一次性枚举无穷个候选点。常见做法是先把容器空间离散成小立方体网格,例如把1200mm×1000mm×1200mm的托盘按20mm网格切分,后续所有计算只在网格上做。网格粒度决定了状态大小和放置精度,粒度太细状态维度爆炸,粒度太粗无法处理一些箱体的尺寸差,通常取箱体最小公因子的1/2到1/4之间。

实际工程里我一般把容器离散成三维的高程矩阵和占用矩阵,分别代表“当前每个柱位已经堆到多高”和“每个格子是否被某个箱子占用”。这样网络输入非常紧凑,推理速度快。需要注意三维网格和图像网格不同,箱子的放置位置一般只能在完整能放下的落点处,不能像目标检测那样输出任意浮点坐标,所以离散化同时也是动作空间的设计前提。

2.2 状态编码:用“最高高度图”加“剩余容量热度图”作为观测

拍脑袋做状态编码往往让神经网络变成黑匣子,且难以收敛。我常用的做法是把状态拆成多通道,让每个通道都有物理含义。

第一个通道是高度图:将容器按俯视平面分成网格列,每列的值是该列当前堆叠的最高高度。第二个通道是可用容量热度图:统计每个网格位置上方剩余空间能容纳的最大箱体体积,简单说给出一张“这里还放得下多少”的热力指示。第三个通道是当前待放置箱子的尺寸二进制平面图,相当于把箱子的长宽投影到网格上,方便网络做匹配。

import numpy as np def encode_state(container: np.ndarray, box: np.ndarray): # container: 已经占用的三维体素数组,shape=(H, L, W) # box: 当前箱子尺寸,shape=(3,) height_map = np.max(container, axis=0) # 第一通道:俯视高度图 capacity_map = np.zeros_like(height_map, dtype=np.float32) L, W = container.shape[1], container.shape[2] for x in range(L): for y in range(W): col_free = int(container.shape[0] - height_map[x, y]) # 只要箱子高度不大于剩余高度,就算能放进这个列位 if box[0] <= col_free: capacity_map[x, y] = box[0] * box[1] * box[2] else: capacity_map[x, y] = 0.0 box_proj = np.zeros((L, W), dtype=np.float32) bl, bw = box[1], box[2] # 这里默认箱子长宽投影到俯视图 box_proj[:bl, :bw] = 1.0 state = np.stack([height_map, capacity_map, box_proj], axis=-1) return state

这段代码的逻辑是把三类物理信息拼成多通道张量。height_map用最大函数对容器体素在高度轴上归约,容量图则是一个简单的扫描填充:只判断“这一列能不能装下当前箱子”,能装就把箱体体积写在对应像素上,不能就置零。这个通道的目的不是给网络一个精确数值,而是告诉它“哪些列位还有利用价值”。box_proj作为待放置箱子的投影,让网络在卷积归纳时能把“箱子占多大面积”和“高度图上有多少凹陷”对应起来。

参数说明:容器体素数组的shape约定是(height, length, width),全部体素只有0和1两种取值,1代表已被占用;height_map的单位是网格数而不是真实毫米,训练时建议统一除以容器高度做归一化。容量图用箱体体积而不用剩余高度,是因为不同箱子尺寸下“能装”和“装得值”是两回事,体积信号更接近放置带来的空间收益。

2.3 动作空间:候选放置点与旋转策略

动作空间设计是三维装箱DQN项目里最容易翻车的地方。如果直接对每个网格位判断放不放,动作空间会膨胀到几十万维,DQN几乎不可能收敛。常见做法是只把“贴着已放箱子或容器壁的角点”作为候选放置点,这也是三维装箱领域里被验证过的空间邻域搜索经验:一个稳定放置位置,至少有一面贴着容器壁,或者贴着已放箱子的面。

def gen_candidate_actions(container, height_map, box, grid_size, support_thresh=0.7): L, W = height_map.shape actions = [] for x in range(L - box[1] + 1): for y in range(W - box[2] + 1): # 只考察贴着容器边界或贴着已有箱体的列位 near_wall = x == 0 or y == 0 or x == L - box[1] or y == W - box[2] near_box = False if x > 0 and height_map[x - 1, y] > 0: near_box = True if y > 0 and height_map[x, y - 1] > 0: near_box = True if not (near_wall or near_box): continue # 检查该区域下方支撑是否足够 support_mask = height_map[x:x + box[1], y:y + box[2]] support_ratio = np.mean(support_mask > 0) if support_ratio < support_thresh: continue place_height = int(np.max(support_mask)) if place_height + box[0] > container.shape[0]: continue actions.append((x, y, place_height)) return actions

这段代码的要点是剪枝。第一剪枝条件是贴着容器壁或贴着已有箱子,排除大量中空悬停位置;第二剪枝条件是支撑率阈值support_thresh,默认0.7,表示该区域至少70%的网格列下方有支撑,这能减少把箱子架空的倾向;第三剪枝是高度检查,防止箱子超出容器顶部。place_height取该区域所有列的最大高度,这样箱子落下来后不会悬空,实际代码里既可以放一个固定朝向,也可以把4个旋转分别展开成多个动作分支。

参数说明:support_thresh越低越容易放低位置,越高越保守,填0.9时基本只允许放到平整面上;旋转处理上建议把每种朝向作为独立动作放进动作空间,网络自己学哪种朝向在当前状态更优,不单独设朝向输出的分支,能减少一个不稳定因素。

2.4 奖励函数:用体积利用率和稳定支撑做奖励,避免一次给太多学分

三维在线装箱的最大收益来自于最终的空间利用率,但网络没法在每一步都看到最终结果。在线场景里奖励必须拆成“当前生效”的信号。我常用的奖励表达式是放置后体积利用的增量、放置稳定性和对后续空间的破坏性惩罚的组合。

def compute_reward(before_volume, after_volume, box_volume, support_ratio, center_offset, punish_factor=0.2): utilization_delta = (after_volume - before_volume) / max(box_volume, 1e-6) stability = support_ratio offset_penalty = punish_factor * center_offset reward = utilization_delta + 0.3 * stability - offset_penalty return reward

逻辑说明:utilization_delta衡量这一放下去增量多少的相对体积利用率,因为增量通常是个接近1但不到1的数字,所以奖励数量级比较稳定。0.3乘上支撑率是稳定性加分,支撑率越接近1说明放得越稳,这个项对防止网络学出“把箱子放成一堵悬空墙”很关键。center_offset是放置位置中心相对于容器中心在高度方向偏移量,惩罚项让网络不要把箱子堆到极高处。

参数说明:0.3这个系数在实验中表现为“稳定性的权重”,调大容易让策略变得保守,只放平整位置;punish_factor默认0.2,如果训练出来的策略频繁把箱子垫高,就把它加到0.5。注意不要让三个项的量纲差得太远,先把utilization_delta、stability、offset_penalty分别统计一遍,确认它们大致在同一个数量级再叠加。

3. DQN网络与训练主循环:Python里的关键实现

3.1 网络结构:用二维双分支Q网络而不是直接吃三维体素

三维体素数据直接丢给3D CNN理论上可行,但动作空间是俯视网格上的候选点,网络最后还是要映射回一个平坦的Q值向量。常见做法是把状态编码成高度图多通道,再用2D卷积提取空间特征。这样计算成本低、收敛快,而且容易debug。我一般用一个小型ResNet风格网络,主干是两个卷积块,每个块包含双卷积和残差连接,最后接全连接层输出Q值向量。Q值向量的长度等于候选动作个数,DQN训练时只在合法动作上取最大值。

import torch import torch.nn as nn class DQN3D(nn.Module): def __init__(self, state_channels, num_actions): super().__init__() self.conv_block1 = nn.Sequential( nn.Conv2d(state_channels, 32, kernel_size=3, padding=1), nn.ReLU(), nn.Conv2d(32, 32, kernel_size=3, padding=1), nn.ReLU(), ) self.conv_block2 = nn.Sequential( nn.Conv2d(32, 64, kernel_size=3, padding=1), nn.ReLU(), nn.Conv2d(64, 64, kernel_size=3, padding=1), nn.ReLU(), ) self.pool = nn.AdaptiveAvgPool2d((4, 4)) self.fc = nn.Sequential( nn.Flatten(), nn.Linear(64 * 4 * 4, 256), nn.ReLU(), nn.Linear(256, num_actions), ) def forward(self, state): x = self.conv_block1(state) x = self.conv_block2(x) x = self.pool(x) return self.fc(x)

逻辑说明:两个卷积块把多通道高度图逐步抽象成局部几何模式,网络会学到类似“凹陷区域”“支撑面”“邻近箱体”的特征。AdaptiveAvgPool把特征图压缩成固定尺寸,这样即使容器网格尺寸变化,网络结构也不用改。最后全连接层输出num_actions维Q值,训练时和动作空间对齐。

参数说明:state_channels对应状态编码的通道数,通常是6(高度图、容量图、箱子投影在4个旋转朝向的其中一个)。num_actions不能用固定值,因为每次状态下的候选动作数不同,常见做法是设定最大候选数MAX_ACTIONS,然后对不足的位置用mask填充,Q值掩码在loss计算时把非法动作的Q值置为-1e9。卷积核3x3、通道数32/64是我试过的最稳妥配置,更大的网络对三维装箱没有明显收益反而更难训。

3.2 经验回放:装箱过程的状态转移太稀疏,必须用优先回放

三维装箱场景天然稀疏且回合长,如果只用均匀回放,大量“箱子放到位”但收益一般的经验会把重要样本稀释掉。优先回放按TD误差给样本加权,误差大的样本更值得学习。实现上不用引入太复杂的库,只需要在普通ReplayBuffer上增加一列优先级。

import random from collections import deque class PrioritizedReplayBuffer: def __init__(self, capacity=20000, alpha=0.6, beta_start=0.4): self.buffer = deque(maxlen=capacity) self.priorities = deque(maxlen=capacity) self.alpha = alpha self.beta = beta_start def push(self, transition, td_error=1.0): priority = (td_error + 1e-5) ** self.alpha self.buffer.append(transition) self.priorities.append(priority) def sample(self, batch_size): probs = np.array(self.priorities) / np.sum(self.priorities) idx = np.random.choice(len(self.buffer), batch_size, p=probs) batch = [self.buffer[i] for i in idx] weights = (len(self.buffer) * probs[idx]) ** (-self.beta) weights /= weights.max() return batch, weights def update_priorities(self, idx, td_errors): for i, e in zip(idx, td_errors): self.priorities[i] = (e + 1e-5) ** self.alpha

逻辑说明:push时给td_error设置默认值1.0,新样本优先级居中,不至于一进来就霸占采样。sample按归一化概率采样,再用重要性权重修正偏差,防止优先回放引入过大的梯度偏差。update_priorities在每次Q网络更新后,把这条样本的TD误差回填。

参数说明:alpha=0.6是优先回放常用强度,alpha=0退化成均匀采样;beta在训练过程中从0.4线性增长到1.0,这个参数控制“修正偏差”力度大小,前期的偏差修正到后期更强,能在训练后期更好地稳定收敛。

3.3 训练主循环:回合内订单流、放置失败怎么办、网络更新的节奏

训练主循环是整条路径的核心。每一步执行“环境步进→经验存储→网络更新→target网络软更新”四步。放置失败时,我给一个负奖励并结束回合,而不是把这个箱子跳过继续;因为在线场景里箱子放不下就是放不下,不能回头。

def train_one_episode(env, q_net, target_net, optimizer, replay, batch_size=128, gamma=0.99, eps=0.3, target_update_steps=500): state = env.reset() done = False step = 0 while not done: actions = env.legal_actions() if random.random() < eps: action = random.choice(actions) else: with torch.no_grad(): q_values = q_net(state_tensor) action = actions[q_values.argmax().item()] next_state, reward, done, info = env.step(action) replay.push((state, action, reward, next_state, done)) if len(replay.buffer) > batch_size: batch, weights = replay.sample(batch_size) states = torch.stack([b[0] for b in batch]) actions_idx = torch.tensor([b[1] for b in batch]) rewards = torch.tensor([b[2] for b in batch]) next_states = torch.stack([b[3] for b in batch]) q_sa = q_net(states).gather(1, actions_idx.unsqueeze(1)).squeeze(1) with torch.no_grad(): next_q = target_net(next_states).max(dim=1)[0] target = rewards + gamma * (1 - torch.tensor(done, dtype=torch.float32)) * next_q loss = (weights * (q_sa - target) ** 2).mean() optimizer.zero_grad() loss.backward() optimizer.step() replay.update_priorities(batch_idx, td_errors) if step % target_update_steps == 0: target_net.load_state_dict(q_net.state_dict()) step += 1

逻辑说明:epsilon-greedy探索和规则混合策略在下一章会细讲。经验存储之后先采样再更新,更新用Q-learning目标value = reward + gamma * max(Q(s’))。注意这里next_q取自target_net而不是q_net,避免目标值和预测值用同一套参数导致发散。target网络每隔一定步数复制一次q_net参数,这种硬更新实现简单,配合大量经验回放也够用。

参数说明:gamma=0.99适合回合较长的装箱场景,若每个回合只有5~10个箱子,可以降到0.95;target_update_steps=500是比较稳的节奏,更新太快容易震荡,太慢则学习速度下降,建议以500为基准,训练曲线波动大就调到1000,收敛慢就调到200。

3.4 训练进度怎么盯:从Q值曲线和放置成功率看收敛

训练时只看loss曲线的习惯要改。DQN的loss下降不一定代表策略变好,更实用的指标是“当前平均放置高度/容器总高”和“放置失败率”。我在训练中每回合打印两类指标:放置成功率和平均体积利用率。放置成功率接近1且利用率稳定提升,说明网络在真正学会装箱,而不是把箱子全堆到一个角落。

训练脚本里我加入一个简单回放评估:每隔20个回合固定用同一个随机种子生成10个测试订单,记录平均利用率。测试订单不参与训练,这样能快速对比策略在不同训练阶段的效果。如果利用率迟迟不涨,回到奖励函数排查,这一步能省下很多盲调网络的时间。

4. 让策略真正能在现场用:订单流生成器与规则混合探索

4.1 用订单流生成器替代固定散货集合,在线场景才能练出来

在线装箱和离线装箱最大的区别是“下一单不可预知”。很多复现项目直接用固定一组箱子做训练,网络学出来只是记住了这批箱子的组合模式,换一批箱子就失效。解决思路是训练时用一个可配置的订单流生成器,每一轮往环境里发射随机生成的箱子序列。

def generate_order_stream(num_boxes, size_range, weight_range): orders = [] for _ in range(num_boxes): l = random.uniform(*size_range["length"]) w = random.uniform(*size_range["width"]) h = random.uniform(*size_range["height"]) wgt = random.uniform(*weight_range) orders.append({"dimension": (l, w, h), "weight": wgt}) return orders

逻辑说明:这个生成器每次调用都产生一组新的订单序列,使训练集不断变化,网络被迫学习“看到当前堆叠和当前箱子就决策”的泛化能力,而不是背下序列。weight字段用于后续的记录,比如重物优先放底层这种业务约束,可以加进奖励或动作筛选里。

参数说明:size_range控制箱体长宽高分布。工业场景常见尺寸在200mm到800mm之间,若容器是1200mm×1000mm,这个分布比较合理。实际使用我建议把尺寸分布和真实出库订单分布对齐,而不是均匀分布,这样训练效果更有指向性。

4.2 规则混合探索:在epsilon-greedy里混入SNF启发式

纯随机探索在三维装箱里效率太低,随机的放置位置大部分是无效的,即使合法也往往很差,网络学到有效经验的速度慢。常见的改进是规则混合探索:在epsilon概率下,一部分动作随机选,一部分动作用空间邻域拟合(SNF)规则来选。

def choose_action(state, actions, q_net, eps, rule_prob=0.5): if random.random() < eps: # 随机探索中有一定概率使用SNF规则挑选候选 if random.random() < rule_prob and actions: # 贪心地选“当前放置位置最低”的动作 return min(actions, key=lambda a: a[2]) return random.choice(actions) with torch.no_grad(): q_values = q_net(state) valid_q = q_values[actions] return actions[valid_q.argmax().item()]

逻辑说明:SNF规则是三维装箱中常见的放置策略之一,其核心思想是“尽量选择最低且最靠近角落的放置点”。把它放进探索策略里,网络在前期就能收集到不少质量较好的经验样本,而不是纯靠随机瞎碰。等网络学到了足够好的策略,Q值的贪心选择会逐步替代规则选择。

参数说明:rule_prob=0.5表示在epsilon随机分支中有一半的概率使用规则,其余一半仍然纯随机,保证探索多样性。随着训练进度可以把rule_prob降到0.2甚至0,避免规则策略限制了网络学出更好的策略。

4.3 在线装箱的评估指标:利用率、重工率和单步耗时都要看

很多项目汇报时只报最终体积利用率,但在线场景还有更关键的指标。一个是“重工率”,即已经放置的箱子有多少需要被重新取出调整。DQN的决策不能回溯,重工率太高说明策略在早期放置就牺牲了后期空间。另一个是单步决策耗时,因为在线环境要求决策在箱子到达窗口内完成。

评估时我习惯把这三个指标一起记录,放在一张表里:

指标含义可接受范围
体积利用率容器被箱体占据体积/容器总容积商业场景一般要求0.7以上
重工率需要重摆放的箱子数/总箱子数越低越好,最好0
单步耗时生成一个放置动作的推理时间目标小于50ms

训练好的网络在Python推理,单步基本在几毫秒到几十毫秒量级,瓶颈通常不在网络前向传播而在候选动作生成。如果候选点生成太慢,可以预计算网格的邻接索引表,把gen_candidate_actions里的双层循环优化成查表。

4.4 数据管理:把订单流和状态图存成文件,训练可复现

训练三维装箱DQN很依赖随机种子,种子一变结果就变。为了能定位问题,我会在训练时把每个回合的订单流和关键状态图保存到本地,包括容器体素数组的dump文件和订单序列的JSON。这样就算后来改了网络结构,也能用同一批训练数据对比前后效果。Python里直接用np.save和json.dump即可,磁盘占用并不大。源代码管理上建议把环境代码、网络代码、训练脚本按模块拆开,环境只暴露step和reset接口,这样后面换策略算法时不用重写环境。

5. 三维装箱DQN训练避坑:5条血泪经验

5.1 现象:训练很久,放置高度还是随机乱跳

原因:状态编码里没有把箱子尺寸与当前容器状态的关系体现出来,网络无法区分“这个箱子放这里是否合适”,Q值对任意动作都趋同。有些状态编码只给了高度图,没给容量信号,导致大量动作对应的Q值几乎一样。

解决:将容量热度图和箱子投影图加到状态通道里,并确保奖励是“体积增量”这种尺度稳定的信号,而不是“未使用空间总量”。体积增量是相对当前这一个箱子的归一化变化,数值范围稳定;未使用空间的总量随回合推进不断变化,网络很难从这种非平稳信号里学到稳定策略。我见过仅去掉容量通道的实验,利用率从0.72掉到0.61。

5.2 现象:训练出来的策略专门把箱子放高,重心不稳

原因:奖励只盯着体积增量,箱子放得再高,只要体积利用到位就得分,缺少稳定性惩罚。我从一次机械臂码垛仿真里看到,模型学会把中等箱子垫在两个大箱子上方,表面看空间利用率不错,但箱体悬空支撑不足,仿真里稍微加一点晃动就垮了。

解决:在奖励函数里加支撑率系数和重心偏移惩罚。先只调支撑率一项,把0.3系数调到0.5,观察放置高度是否下降;如果还不降,就再加重心偏移惩罚,惩罚系数从0.2起步。另外可以在候选动作生成阶段就把支撑率阈值设到0.7以上,从源头砍掉不稳定的候选位置,效果比只改奖励更直接。

5.3 现象:候选动作太多,训练速度极慢

原因:对每个网格列生成候选点,动作空间上十万维,DQN全连接层参数量急剧膨胀。有一次训练跑了8个小时利用率还是原地踏步,一看日志每次状态生成动作列表花了近1秒,网络更新反而只占很少时间。

解决:只用贴着容器壁或已放置箱子的候选点,并且增加支撑率阈值。优化后动作数量通常降到几百到几千个,训练速度能提升一个数量级。同时把Q值输出维度固定为最大候选数,用mask处理不足的位置,避免每次向前传播都重新建网络。

5.4 现象:离线测试很好,一换订单流就崩

原因:训练时使用的订单流尺寸分布和测试分布不一致,网络见过的大箱子少,遇到大箱子就胡乱放置。通常表现为:离线测试用的是同分布数据,利用率好看;到了新的订单流,箱子尺寸范围变了,策略立刻退化。

解决:订单流生成器要按真实业务分布配置尺寸范围、重量范围,并在训练中周期性注入“极端箱型”,比如超大箱占5%左右的比例,让网络见过这种样本。还可以在评估阶段用三个不同的订单流验证:一个同分布、一个大箱偏多、一个小箱偏多,三个都达标才算策略稳定。

5.5 现象:训练后期loss忽高忽低,Q值发散

原因:TD误差过大,target网络更新太快,当前网络和target网络差距太大。普通DQN的target和当前Q用同一套经验更新,如果target更新频率太高,目标值本身会飘,loss自然跟着震。

解决:调低学习率到1e-4量级,把target_update_steps从500调到1000或2000;如果还发散,把单步更新经验回放batch从128降到64,降低单次更新的梯度噪声。这个优先级比换网络结构高,我在同样环境里只动这个参数,成功率就能稳定下来。

6. 一个验证小技巧:单步推演脚本观察Q值决策

模型训完不能只看指标,还要能在具体状态上看懂它的决策。我写过一个非常小的单步推演脚本,专门用来手动检查“当前状态网络会不会选一个离谱的位置”。

def inspect_step(env, q_net, state, actions): with torch.no_grad(): q_values = q_net(torch.from_numpy(state).float().unsqueeze(0)).squeeze(0) ranked = sorted(actions, key=lambda a: q_values[a].item(), reverse=True) print("Top-3 动作:") for act in ranked[:3]: print(f"位置={act}, Q={q_values[act].item():.4f}") worst = ranked[-1] print(f"最差动作={worst}, Q={q_values[worst].item():.4f}") return ranked

这个脚本每次选一个特定订单状态,打印Top-3和最差的动作Q值。我拿它做过一次很有价值的验证:网络在某个状态把大箱子放到一个角落,但旁边明明有更平整的支撑面,Q值却非常接近,说明网络对“平整支撑面”的感知还不够强。后来在状态编码里补充了支撑率通道,问题就消失了。

再往深一点,可以把每步的Q值存下来,和放置后的实际单步奖励做对比,一旦出现“Q值很高但奖励非常低”的情况,说明网络还没有对齐环境规则,优先检查奖励函数而不是网络结构。这也是我踩过多次坑之后的习惯:DQN不收敛先怀疑奖励、再怀疑编码、最后才怀疑模型大小。参数上我习惯把每轮训练seed固定、订单流落盘、Q值日志保留一个版本,这样前后对比从来不靠记忆,全部翻文件就能查。希望这篇笔记里的建模细节和踩坑记录能帮你少走几轮弯路,希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询