mirror of
https://github.com/wassname/Deep-reinforcement-learning-with-pytorch.git
synced 2026-09-10 11:40:57 +08:00
69 KiB
69 KiB
In [2]:
# 创建所有合法走子UCI,size 2086
def create_uci_labels():
labels_array = []
letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']
numbers = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
Advisor_labels = ['d7e8', 'e8d7', 'e8f9', 'f9e8', 'd0e1', 'e1d0', 'e1f2', 'f2e1',
'd2e1', 'e1d2', 'e1f0', 'f0e1', 'd9e8', 'e8d9', 'e8f7', 'f7e8']
Bishop_labels = ['a2c4', 'c4a2', 'c0e2', 'e2c0', 'e2g4', 'g4e2', 'g0i2', 'i2g0',
'a7c9', 'c9a7', 'c5e7', 'e7c5', 'e7g9', 'g9e7', 'g5i7', 'i7g5',
'a2c0', 'c0a2', 'c4e2', 'e2c4', 'e2g0', 'g0e2', 'g4i2', 'i2g4',
'a7c5', 'c5a7', 'c9e7', 'e7c9', 'e7g5', 'g5e7', 'g9i7', 'i7g9']
for l1 in range(9):
for n1 in range(10):
destinations = [(t, n1) for t in range(9)] + \
[(l1, t) for t in range(10)] + \
[(l1 + a, n1 + b) for (a, b) in
[(-2, -1), (-1, -2), (-2, 1), (1, -2), (2, -1), (-1, 2), (2, 1), (1, 2)]] # 马走日
for (l2, n2) in destinations:
if (l1, n1) != (l2, n2) and l2 in range(9) and n2 in range(10):
move = letters[l1] + numbers[n1] + letters[l2] + numbers[n2]
labels_array.append(move)
for p in Advisor_labels:
labels_array.append(p)
for p in Bishop_labels:
labels_array.append(p)
return labels_arrayIn [36]:
def tower_loss(self, inputs_batch, pi_batch, z_batch, i):
# 卷积块
with tf.variable_scope('init'):
layer = tf.layers.conv2d(inputs_batch, self.filters_size, 3, padding='SAME') # filters 128(or 256)
layer = tf.contrib.layers.batch_norm(layer, center=False, epsilon=1e-5, fused=True,
is_training=self.training, activation_fn=tf.nn.relu) # epsilon = 0.25
# 残差块
with tf.variable_scope("residual_block"):
for _ in range(self.res_block_nums):
layer = self.residual_block(layer)
# 策略头
with tf.variable_scope("policy_head"):
policy_head = tf.layers.conv2d(layer, 2, 1, padding='SAME')
policy_head = tf.contrib.layers.batch_norm(policy_head, center=False, epsilon=1e-5, fused=True,
is_training=self.training, activation_fn=tf.nn.relu)
# print(self.policy_head.shape) # (?, 9, 10, 2)
policy_head = tf.reshape(policy_head, [-1, 9 * 10 * 2])
policy_head = tf.contrib.layers.fully_connected(policy_head, self.prob_size, activation_fn=None)
self.policy_head.append(policy_head) # 保存多个gpu的策略头结果(走子概率向量)
# 价值头
with tf.variable_scope("value_head"):
value_head = tf.layers.conv2d(layer, 1, 1, padding='SAME')
value_head = tf.contrib.layers.batch_norm(value_head, center=False, epsilon=1e-5, fused=True,
is_training=self.training, activation_fn=tf.nn.relu)
# print(self.value_head.shape) # (?, 9, 10, 1)
value_head = tf.reshape(value_head, [-1, 9 * 10 * 1])
value_head = tf.contrib.layers.fully_connected(value_head, 256, activation_fn=tf.nn.relu)
value_head = tf.contrib.layers.fully_connected(value_head, 1, activation_fn=tf.nn.tanh)
self.value_head.append(value_head) # 保存多个gpu的价值头结果(胜率)
# 损失
with tf.variable_scope("loss"):
policy_loss = tf.nn.softmax_cross_entropy_with_logits(labels=pi_batch, logits=policy_head)
policy_loss = tf.reduce_mean(policy_loss)
# value_loss = tf.squared_difference(z_batch, value_head)
value_loss = tf.losses.mean_squared_error(labels=z_batch, predictions=value_head)
value_loss = tf.reduce_mean(value_loss)
tf.summary.scalar('mse_tower_{}'.format(i), value_loss)
regularizer = tf.contrib.layers.l2_regularizer(scale=self.c_l2)
regular_variables = tf.trainable_variables()
l2_loss = tf.contrib.layers.apply_regularization(regularizer, regular_variables)
# loss = value_loss - policy_loss + l2_loss
loss = value_loss + policy_loss + l2_loss # softmax交叉熵损失 + MSE + l2损失
self.loss += loss # 多个gpu的loss总和
tf.summary.scalar('loss_tower_{}'.format(i), loss)
with tf.variable_scope("accuracy"):
# Accuracy 这个准确率是预测概率向量和MCTS的概率向量的比较
correct_prediction = tf.equal(tf.argmax(policy_head, 1), tf.argmax(pi_batch, 1))
correct_prediction = tf.cast(correct_prediction, tf.float32)
accuracy = tf.reduce_mean(correct_prediction, name='accuracy')
self.accuracy += accuracy
tf.summary.scalar('move_accuracy_tower_{}'.format(i), accuracy)
return loss
def residual_block(self, in_layer):
orig = tf.identity(in_layer)
layer = tf.layers.conv2d(in_layer, self.filters_size, 3, padding='SAME') # filters 128(or 256)
layer = tf.contrib.layers.batch_norm(layer, center=False, epsilon=1e-5, fused=True,
is_training=self.training, activation_fn=tf.nn.relu)
layer = tf.layers.conv2d(layer, self.filters_size, 3, padding='SAME') # filters 128(or 256)
layer = tf.contrib.layers.batch_norm(layer, center=False, epsilon=1e-5, fused=True, is_training=self.training)
out = tf.nn.relu(tf.add(orig, layer))
return outIn [10]:
def train_step(self, positions, probs, winners, learning_rate):
feed_dict = {
self.inputs_: positions,
self.training: True,
self.learning_rate: learning_rate,
self.pi_: probs,
self.z_: winners
}
_, accuracy, loss, global_step, summary = self.sess.run([self.train_op, self.accuracy, self.loss, self.global_step, self.summaries_op], feed_dict=feed_dict)
self.train_writer.add_summary(summary, global_step)
return accuracy, loss, global_stepIn [12]:
#@profile
def forward(self, positions): # , probs, winners
# print("positions.shape : ", positions.shape)
positions = np.array(positions)
batch_n = positions.shape[0] // self.num_gpus
alone = positions.shape[0] % self.num_gpus
if alone != 0: # 判断是否不能被gpu均分
if(positions.shape[0] != 1): # 如果不止1份数据。因为有可能输入数据的长度是1,这样肯定不能被多gpu均分了。
feed_dict = {
self.inputs_: positions[:positions.shape[0] - alone], # 先将能均分的这部分数据传入神经网络
self.training: False
}
action_probs, value = self.sess.run([self.policy_head, self.value_head], feed_dict=feed_dict)
action_probs, value = np.vstack(action_probs), np.vstack(value)
new_positions = positions[positions.shape[0] - alone:] # 取余下的这部分数据
pos_lst = []
while len(pos_lst) == 0 or (np.array(pos_lst).shape[0] * np.array(pos_lst).shape[1]) % self.num_gpus != 0:
pos_lst.append(new_positions) # 将余下的这部分数据堆叠起来,直到数量的长度能被gpu均分
if(len(pos_lst) != 0):
shape = np.array(pos_lst).shape
pos_lst = np.array(pos_lst).reshape([shape[0] * shape[1], 9, 10, 14])
# 将数据传入网络,得到不能被gpu均分的数据的计算结果
feed_dict = {
self.inputs_: pos_lst,
self.training: False
}
action_probs_2, value_2 = self.sess.run([self.policy_head, self.value_head], feed_dict=feed_dict)
# print("action_probs_2.shape : ", np.array(action_probs_2).shape)
# print("value_2.shape : ", np.array(value_2).shape)
action_probs_2, value_2 = action_probs_2[0], value_2[0]
# print("------------------------")
# print("action_probs_2.shape : ", np.array(action_probs_2).shape)
# print("value_2.shape : ", np.array(value_2).shape)
if(positions.shape[0] != 1): # 多个数据的计算结果
action_probs = np.concatenate((action_probs, action_probs_2),axis=0)
value = np.concatenate((value, value_2),axis=0)
# print("action_probs.shape : ", np.array(action_probs).shape)
# print("value.shape : ", np.array(value).shape)
return action_probs, value
else: # 只有1个数据的计算结果
return action_probs_2, value_2
else:
# 正常情况,能被gpu均分
feed_dict = {
self.inputs_: positions,
self.training: False
}
action_probs, value = self.sess.run([self.policy_head, self.value_head], feed_dict=feed_dict)
# print("np.vstack(action_probs) shape : ", np.vstack(action_probs).shape)
# print("np.vstack(value) shape : ", np.vstack(value).shape)
# 将多个gpu的计算结果堆叠起来返回
return np.vstack(action_probs), np.vstack(value)In [15]:
def run(self):
batch_iter = 0
try:
while(True):
batch_iter += 1
play_data, episode_len = self.selfplay() # 自我对弈,返回下棋数据
print("batch i:{}, episode_len:{}".format(batch_iter, episode_len))
extend_data = []
for state, mcts_prob, winner in play_data:
states_data = self.mcts.state_to_positions(state)
extend_data.append((states_data, mcts_prob, winner)) # 将棋盘特征平面、MCTS算出的概率向量、胜率保存起来
self.data_buffer.extend(extend_data)
if len(self.data_buffer) > self.batch_size: # 保存的数据达到指定数量时
self.policy_update() # 开始训练
except KeyboardInterrupt:
self.log_file.close()
self.policy_value_netowrk.save(self.global_step)In [19]:
def policy_update(self):
"""update the policy-value net"""
# 从数据中随机抽取一部分数据
mini_batch = random.sample(self.data_buffer, self.batch_size)
#print("training data_buffer len : ", len(self.data_buffer))
state_batch = [data[0] for data in mini_batch]
mcts_probs_batch = [data[1] for data in mini_batch]
winner_batch = [data[2] for data in mini_batch]
# print(np.array(winner_batch).shape)
# print(winner_batch)
winner_batch = np.expand_dims(winner_batch, 1)
# print(winner_batch.shape)
# print(winner_batch)
start_time = time.time()
old_probs, old_v = self.mcts.forward(state_batch) # 先通过正向传播预测下网络输出结果,用于计算训练后的KL散度
for i in range(self.epochs): # 一共训练5次
# 训练网络。敲黑板!这里的学习率需要特别注意。我在aws上用的是g2.2xlarge,24小时只能下差不多200盘棋,很慢。
# 所以学习率是在这里是动态调整的。当然您也可以使用指数衰减学习率,在上面定义学习率的地方就需要修改成类似下面这句:
# self.learning_rate = tf.maximum(tf.train.exponential_decay(0.001, self.global_step, 1e3, 0.66), 1e-5)
# 然后这里训练网络的地方学习率就不用作为参数传递了,也可以在训练网络函数里面不使用传递的学习率参数。
accuracy, loss, self.global_step = self.policy_value_netowrk.train_step(state_batch, mcts_probs_batch, winner_batch,
self.learning_rate * self.lr_multiplier) #
new_probs, new_v = self.mcts.forward(state_batch) #使用训练后的新网络预测结果,跟之前的结果计算KL散度
kl_tmp = old_probs * (np.log((old_probs + 1e-10) / (new_probs + 1e-10)))
# print("kl_tmp.shape", kl_tmp.shape)
kl_lst = []
for line in kl_tmp:
# print("line.shape", line.shape)
all_value = [x for x in line if str(x) != 'nan' and str(x)!= 'inf'] #除去inf值
kl_lst.append(np.sum(all_value))
kl = np.mean(kl_lst)
# kl = scipy.stats.entropy(old_probs, new_probs)
# kl = np.mean(np.sum(old_probs * (np.log(old_probs + 1e-10) - np.log(new_probs + 1e-10)), axis=1))
if kl > self.kl_targ * 4: # early stopping if D_KL diverges badly
break
self.policy_value_netowrk.save(self.global_step)
print("train using time {} s".format(time.time() - start_time))
# 通过计算调整学习率乘子
# adaptively adjust the learning rate
if kl > self.kl_targ * 2 and self.lr_multiplier > 0.1:
self.lr_multiplier /= 1.5
elif kl < self.kl_targ / 2 and self.lr_multiplier < 10:
self.lr_multiplier *= 1.5
explained_var_old = 1 - np.var(np.array(winner_batch) - old_v.flatten()) / np.var(np.array(winner_batch))
explained_var_new = 1 - np.var(np.array(winner_batch) - new_v.flatten()) / np.var(np.array(winner_batch))
print(
"kl:{:.5f},lr_multiplier:{:.3f},loss:{},accuracy:{},explained_var_old:{:.3f},explained_var_new:{:.3f}".format(
kl, self.lr_multiplier, loss, accuracy, explained_var_old, explained_var_new))
self.log_file.write("kl:{:.5f},lr_multiplier:{:.3f},loss:{},accuracy:{},explained_var_old:{:.3f},explained_var_new:{:.3f}".format(
kl, self.lr_multiplier, loss, accuracy, explained_var_old, explained_var_new) + '\n')
self.log_file.flush()In [22]:
def selfplay(self):
self.game_borad.reload() # 初始化棋盘
states, mcts_probs, current_players = [], [], []
z = None
game_over = False
winnner = ""
start_time = time.time()
while(not game_over): # 下棋循环,结束条件是分出胜负
action, probs, win_rate = self.get_action(self.game_borad.state, self.temperature) # 通过MCTS算出下哪一步棋
################################################
# 这部分代码是跟我的设计有关的。因为在输入特征平面中我没有使用颜色特征,
# 所以传给神经网络数据时,要把当前选手转换成红色(先手),转换的其实是棋盘的棋子位置
# 这样神经网络预测的始终是红色先手方向该如何下棋
state, palyer = self.mcts.try_flip(self.game_borad.state, self.game_borad.current_player, self.mcts.is_black_turn(self.game_borad.current_player))
states.append(state)
prob = np.zeros(labels_len)
# 神经网络返回的概率向量也需要转换,假如当前选手是黑色,转换成红色后,由于棋盘位置的变化,概率向量(走子集合)是基于红色棋盘的
# 要把走子action转换成黑色选手的方向才行。明白我的意思吧?
if self.mcts.is_black_turn(self.game_borad.current_player):
for idx in range(len(probs[0][0])):
act = "".join((str(9 - int(a)) if a.isdigit() else a) for a in probs[0][0][idx])
prob[label2i[act]] = probs[0][1][idx]
else:
for idx in range(len(probs[0][0])):
prob[label2i[probs[0][0][idx]]] = probs[0][1][idx]
mcts_probs.append(prob)
################################################
current_players.append(self.game_borad.current_player)
last_state = self.game_borad.state
self.game_borad.state = GameBoard.sim_do_action(action, self.game_borad.state) # 在棋盘上下算出的这步棋,得到新的棋盘状态
self.game_borad.round += 1 # 更新回合数
self.game_borad.current_player = "w" if self.game_borad.current_player == "b" else "b" # 切换当前选手
if is_kill_move(last_state, self.game_borad.state) == 0: # 刚刚下的棋是否吃子了
self.game_borad.restrict_round += 1 # 更新没有进展回合数
else:
self.game_borad.restrict_round = 0
if (self.game_borad.state.find('K') == -1 or self.game_borad.state.find('k') == -1):
# 条件满足说明将/帅被吃了,游戏结束
z = np.zeros(len(current_players))
if (self.game_borad.state.find('K') == -1):
winnner = "b"
if (self.game_borad.state.find('k') == -1):
winnner = "w"
z[np.array(current_players) == winnner] = 1.0
z[np.array(current_players) != winnner] = -1.0
game_over = True
print("Game end. Winner is player : ", winnner, " In {} steps".format(self.game_borad.round - 1))
elif self.game_borad.restrict_round >= 60: # 60回合没有进展(吃子),平局
z = np.zeros(len(current_players))
game_over = True
print("Game end. Tie in {} steps".format(self.game_borad.round - 1))
# 认输的部分没有实现
# elif(self.mcts.root.v < self.resign_threshold):
# pass
# elif(self.mcts.root.Q < self.resign_threshold):
# pass
if(game_over):
self.mcts.reload() # 游戏结束,重置棋盘
print("Using time {} s".format(time.time() - start_time))
return zip(states, mcts_probs, z), len(z) # 返回下棋数据Warning:
Output truncated. This notebook contains too many cells to display efficiently.