You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

双网络DQN实现Blackjack RL AI遇TypeError及算法问题求助

解决Blackjack双DQN的TypeError及算法优化建议

首先,先解决你遇到的TypeError: Using a tf.Tensor as a Python bool is not allowed错误:

错误原因与修复

这个错误出现在你更新目标网络的代码行:

sess.run(update1,update2,update3)

TensorFlow的sess.run()函数第一个参数可以是单个Tensor/操作,或者多个操作组成的列表,而你这里把三个assign操作分开传递,导致第二个参数update2被当成了feed_dict参数(要求是字典类型),内部代码尝试判断这个Tensor是否为非空时,就触发了“不能把Tensor当作Python布尔值”的错误。

修复方法:把三个更新操作放在列表里执行,同时调整更新时机(当前在每个step里判断更新,会导致目标网络被频繁更新,应该放在每个episode结束后):

# 把这段代码从step循环里移到episode循环的末尾
if i % 10 == 0:
    sess.run([update1, update2, update3])

DQN算法的潜在问题与优化建议

你的当前代码实现了双网络DQN的雏形,但缺少一些核心机制,导致训练不稳定、成功率可能达不到预期,以下是关键改进点:

1. 加入经验回放(Experience Replay)

DQN的核心特性之一就是经验回放,它能打破样本的时间相关性,提升训练稳定性。你当前是在线学习(每一步直接用当前样本训练),很容易导致模型震荡。

实现思路:

  • 创建一个经验池,存储每个step的(状态, 动作, 奖励, 下一状态, 是否结束)
  • 每次训练时,从经验池中随机采样一批样本(比如32个)来更新网络,而不是用当前单一样本

2. 优化状态编码

你当前的状态编码生成了600维的one-hot向量,但Blackjack的有效状态空间其实更小:

  • 玩家手牌总和:12-21(共10种,因为总和<12时最优策略必为Hit,无需学习)
  • 庄家明牌:1-10(共10种)
  • 是否有可用A:0/1(共2种)
    总共有10*10*2=200种有效状态,这样可以减少网络参数,提升训练效率。

修改one_hot函数:

def one_hot(x):
    # 限制玩家总和在12-21之间,小于12的统一按12处理,大于21的按21处理
    player_sum = max(12, min(x[0], 21))
    dealer_card = x[1]
    has_ace = int(x[2])
    # 计算索引:(player_sum-12)*10*2 + (dealer_card-1)*2 + has_ace
    idx = (player_sum - 12) * 20 + (dealer_card - 1) * 2 + has_ace
    s = np.identity(200)
    return s[idx].reshape(1, 200)

3. 调整学习率与优化器

当前learning_rate=0.5过高,会导致训练震荡,难以收敛。建议改用Adam优化器(比SGD更稳定),学习率设为0.001左右:

# 替换原来的GradientDescentOptimizer
train = tf.train.AdamOptimizer(learning_rate=0.001).minimize(loss)

4. 优化探索率(ε)衰减策略

当前的ε衰减是1./((i/100)+1),后期ε会趋近于0,完全停止探索,容易过拟合。建议改成带最低值的指数衰减:

# 在episode循环外初始化
e = 1.0
e_decay_rate = 0.995
e_min = 0.01

# 在每个episode开始时更新ε
e = max(e_min, e * e_decay_rate)

5. 替换激活函数为ReLU

当前用tanh作为激活函数,容易出现梯度饱和问题,建议改用ReLU:

layer1 = tf.nn.relu(tf.matmul(X, W1))
layer2 = tf.nn.relu(tf.matmul(layer1, W2))
# 目标网络同理
layer3 = tf.nn.relu(tf.matmul(X1, W4))
layer4 = tf.nn.relu(tf.matmul(layer3, W5))

6. 调整目标网络更新频率

当前每10个episode更新一次目标网络是可行的,但更常见的是按step数更新(比如每1000个step更新一次),你可以根据训练情况调整。


修改后的完整示例代码

import gym
import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np
from collections import deque
import random

def one_hot(x):
    # 优化状态编码,只保留有效状态
    player_sum = max(12, min(x[0], 21))
    dealer_card = x[1]
    has_ace = int(x[2])
    idx = (player_sum - 12) * 20 + (dealer_card - 1) * 2 + has_ace
    s = np.identity(200)
    return s[idx].reshape(1, 200)

env = gym.make('Blackjack-v0')
learning_rate = 0.001
state_number = 200
action_number = 2

# 主网络(预测网络)
X = tf.placeholder(tf.float32, shape=[None, state_number], name='input_data')
W1 = tf.Variable(tf.random_uniform([state_number, 128], 0, 0.01))
layer1 = tf.nn.relu(tf.matmul(X, W1))
W2 = tf.Variable(tf.random_uniform([128, 256], 0, 0.01))
layer2 = tf.nn.relu(tf.matmul(layer1, W2))
W3 = tf.Variable(tf.random_uniform([256, action_number], 0, 0.01))
Qpred = tf.matmul(layer2, W3)

# 目标网络
X1 = tf.placeholder(shape=[None, state_number], dtype=tf.float32)
W4 = tf.Variable(tf.random_uniform([state_number, 128], 0, 0.01))
layer3 = tf.nn.relu(tf.matmul(X1, W4))
W5 = tf.Variable(tf.random_uniform([128, 256], 0, 0.01))
layer4 = tf.nn.relu(tf.matmul(layer3, W5))
W6 = tf.Variable(tf.random_uniform([256, action_number], 0, 0.01))
target_Q = tf.matmul(layer4, W6)

# 目标网络更新操作
update_target = [W4.assign(W1), W5.assign(W2), W6.assign(W3)]

Y = tf.placeholder(shape=[None, action_number], dtype=tf.float32)
loss = tf.reduce_mean(tf.square(Y - Qpred))
train = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(loss)

num_episodes = 10000
dis = 0.99
rList = []
# 经验回放池
replay_buffer = deque(maxlen=2000)
batch_size = 32

# 探索率参数
e = 1.0
e_decay_rate = 0.995
e_min = 0.01

init = tf.global_variables_initializer()
with tf.Session() as sess:
    sess.run(init)
    # 初始化目标网络
    sess.run(update_target)
    for i in range(num_episodes):
        s = env.reset()
        rALL = 0
        done = False
        
        # 更新探索率
        e = max(e_min, e * e_decay_rate)
        
        while not done:
            # 状态编码
            s_encoded = one_hot(s)
            # 选择动作
            Qs = sess.run(Qpred, feed_dict={X: s_encoded})
            if np.random.rand(1) < e:
                a = env.action_space.sample()
            else:
                a = np.argmax(Qs)
            
            s1, reward, done, _ = env.step(a)
            s1_encoded = one_hot(s1)
            
            # 存储经验到回放池
            replay_buffer.append((s_encoded, a, reward, s1_encoded, done))
            
            # 当回放池足够大时,采样训练
            if len(replay_buffer) > batch_size:
                minibatch = random.sample(replay_buffer, batch_size)
                states = np.vstack([x[0] for x in minibatch])
                actions = [x[1] for x in minibatch]
                rewards = [x[2] for x in minibatch]
                next_states = np.vstack([x[3] for x in minibatch])
                dones = [x[4] for x in minibatch]
                
                # 计算目标Q值
                target_Q_vals = sess.run(target_Q, feed_dict={X1: next_states})
                Y_vals = sess.run(Qpred, feed_dict={X: states})
                
                for j in range(batch_size):
                    if dones[j]:
                        Y_vals[j, actions[j]] = rewards[j]
                    else:
                        Y_vals[j, actions[j]] = rewards[j] + dis * np.max(target_Q_vals[j])
                
                # 训练主网络
                sess.run(train, feed_dict={X: states, Y: Y_vals})
            
            # 统计奖励
            if reward == 1:
                rALL += 1
            s = s1
        
        # 每10个episode更新一次目标网络
        if i % 10 == 0:
            sess.run(update_target)
        
        rList.append(rALL)
        
        # 每100个episode打印一次当前胜率
        if i % 100 == 0:
            print(f"Episode {i}, Current Success Rate: {sum(rList[-100:])/100:.2f}")

print(f'Final success rate: {sum(rList)/num_episodes:.2f}')
print("Final Q-table values summary (sample):")
# 可以打印一些关键状态的Q值示例
sample_state = (18, 5, True)
sample_encoded = one_hot(sample_state)
q_vals = sess.run(Qpred, feed_dict={X: sample_encoded})
print(f"State: {sample_state} -> Q(Hit): {q_vals[0][0]:.2f}, Q(Stand): {q_vals[0][1]:.2f}")

预期效果

加入经验回放和上述优化后,你的DQN模型应该能收敛到更高的胜率(通常Blackjack的最优策略胜率在42%-45%左右),比你之前的38%有明显提升。

内容的提问来源于stack exchange,提问作者seunghyung lee

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 09:32:39