如何使用Keras构建可从多候选项中选择最优解的神经网络
Keras实现跳棋走法预测的正确方案
你提到的两种思路本质上可以合并,不需要二选一:用权重共享的并行评分结构,既能实现单网络单次推理输出所有走法的概率,又完全满足你提的两个约束条件,实现难度也很低。
- 首先解决输入形状不固定的问题
跳棋6x6棋盘下单回合的可行走法有明确上限,你可以先统计自己规则下的单回合最大走法数量(比如常规简化跳棋单回合最多20种以内走法),把候选动作的固定长度N设为这个最大值。实际对局中走法数量不足最大值时,用全0张量填充多余的动作位,同时生成一个形状为(max_N,)的掩码矩阵:真实可行走法对应的位置标1,填充的无效位置标0。
这种处理方式完全符合Dense层对固定输入形状的要求,后续计算时只要把无效位的得分屏蔽,就不会影响最终softmax的结果。
- 核心网络结构搭建
因为你不需要在候选动作之间建立关联,完全没必要加注意力、序列建模这类会做动作间信息交互的层,直接用独立共享的评分头对每个动作单独打分即可:
- 先做全局状态编码:把形状为
(6,6)的棋盘状态、形状为(1,1)的玩家标识分别做特征提取——棋盘可以先过卷积层再展平,也可以直接展平后接Dense层映射为固定长度的特征向量,玩家标识直接接Dense层映射到对应维度,两者拼接得到全局状态特征向量。 - 把全局状态特征向量复制max_N份,和形状为
(max_N,6,6)的每个候选动作的特征在特征维度拼接,得到每个动作和全局状态配对的联合特征,形状为(max_N, 单个联合特征的长度)。 - 用
TimeDistributed包裹的Dense层作为评分头,对每个位置的联合特征独立计算得分——因为TimeDistributed会让所有位置共享同一套Dense权重,本质上和你之前想的“单个动作+棋盘输入算评分”的逻辑完全一致,只是把N次串行计算改成了并行,速度快很多。这一步输出的是每个动作的原始标量得分,形状为(max_N,)。
- 最终概率输出
把原始得分里填充的无效动作位的得分替换成极小值(比如-1e9),再对整个得分向量做softmax,得到的结果里有效动作位的概率和为1,无效填充位的概率趋近于0,截取有效位置的结果就是你需要的长度为实际可行走法数量的概率分布。
简单Keras实现参考
import tensorflow as tf from tensorflow.keras import layers, Model # 替换成你自己统计的单回合最大可行走法数 MAX_MOVE_COUNT = 20 # 固定形状的输入层 board_input = layers.Input(shape=(6, 6), name="board_state") player_input = layers.Input(shape=(1,), name="player_id") move_input = layers.Input(shape=(MAX_MOVE_COUNT, 6, 6), name="candidate_moves") valid_mask_input = layers.Input(shape=(MAX_MOVE_COUNT,), name="valid_move_mask") # 编码全局公共特征 board_feat = layers.Flatten()(board_input) board_feat = layers.Dense(128, activation="relu")(board_feat) player_feat = layers.Dense(32, activation="relu")(player_input) global_feat = layers.Concatenate()([board_feat, player_feat]) # 全局特征复制后和每个动作特征拼接 global_feat_repeat = layers.RepeatVector(MAX_MOVE_COUNT)(global_feat) move_feat = layers.TimeDistributed(layers.Flatten())(move_input) move_feat = layers.TimeDistributed(layers.Dense(64, activation="relu"))(move_feat) combined_feat = layers.Concatenate()([global_feat_repeat, move_feat]) # 共享权重计算每个动作的原始得分 raw_scores = layers.TimeDistributed(layers.Dense(1))(combined_feat) raw_scores = layers.Flatten()(raw_scores) # 屏蔽无效填充动作,计算softmax概率 masked_scores = layers.Lambda( lambda x: tf.where(x[1] == 1, x[0], -1e9) )([raw_scores, valid_mask_input]) output = layers.Softmax()(masked_scores) model = Model( inputs=[board_input, player_input, move_input, valid_mask_input], outputs=output )
训练注意点
训练时直接用多分类交叉熵作为损失即可,标签构造和输入逻辑一致:长度为max_N的向量,最优走法对应的位置标1,其余位置(包括填充位)标0,不需要额外处理填充位的标签。
内容的提问来源于stack exchange,提问作者Blackfire2122
相关产品推荐
相关产品推荐

