# [Deep Q Learning] pytorch 从零开始建立一个简单的DQN--走迷宫游戏

https://blog.csdn.net/mahuatengmmp/article/details/100627005

pytorch
opencv
numpy

# 首先建立一个游戏环境

### 场景

[[1,0,0,0,0],
[0,0,0,3,0],
[0,0,0,0,0],
[0,3,0,0,0],
[0,0,0,0,2],]
1是棋子位置，2是终点位置，3是坑的位置

### 奖励值

(奖励值这一点我试过很多，发现奖励值规则设置的越复杂，那么神经网络将会学习的更加好)

## 代码部分

game.py

import cv2
import numpy as np
import random
import time

class Env():
def __init__(self):
super(Env, self).__init__()
self.action_space = ['u', 'd', 'l', 'r']
self.n_actions = len(self.action_space)

#建立虚拟环境
def start_env(self):
self.migong=[[1,0,0,0,0],
[0,0,0,3,0],
[0,0,0,0,0],
[0,3,0,0,0],
[0,0,0,0,2],]
self.x1,self.y1=0,0
self.end_game=0
return self.migong

def display(self):
self.display1=np.ones((300,300,3),dtype=np.uint8)
self.display1=np.array(np.where(self.display1==1,255,0),dtype=np.uint8)
for i in range(5):
cv2.line(self.display1,(i*60,0),(i*60,300),(0,0,0),1)
cv2.line(self.display1,(0,i*60),(300,i*60),(0,0,0),1)
for x in range(5):
for y in range(5):
if self.migong[y][x]==1:
cv2.circle(self.display1,(x*60+30,y*60+30),25,(255,0,0),-1)
if self.migong[y][x]==2:
cv2.circle(self.display1,(x*60+30,y*60+30),25,(0,255,0),-1)
if self.migong[y][x]==3:
cv2.circle(self.display1,(x*60+30,y*60+30),25,(0,0,255),-1)

cv2.imshow('1',self.display1)
cv2.waitKey(1)

def step(self, action):
r=0
#['u'0, 'd'1, 'l'2, 'r'3]
if action==0:
if self.y1==0:
r=-0.5
else:
self.migong[self.y1][self.x1]=0
self.migong[self.y1-1][self.x1]=1
self.y1-=1
if self.y1==1 and self.x1==3:
self.end_game=1
r=-1
elif self.y1==3 and self.x1==1:
self.end_game=1
r=-1
elif self.y1==4 and self.x1==4:
self.end_game=2
r=1
if action==1:
if self.y1==4:
r=-0.5
else:
self.migong[self.y1][self.x1]=0
self.migong[self.y1+1][self.x1]=1
self.y1+=1
if self.y1==1 and self.x1==3:
self.end_game=1
r=-1
elif self.y1==3 and self.x1==1:
self.end_game=1
r=-1
elif self.y1==4 and self.x1==4:
self.end_game=2
r=1
if action==2:
if self.x1==0:
r=-0.5
else:
self.migong[self.y1][self.x1]=0
self.migong[self.y1][self.x1-1]=1
self.x1-=1
if self.y1==1 and self.x1==3:
self.end_game=1
r=-1
elif self.y1==3 and self.x1==1:
self.end_game=1
r=-1
elif self.y1==4 and self.x1==4:
self.end_game=2
r=1
if action==3:
if self.x1==4:
r=-0.5
else:
self.migong[self.y1][self.x1]=0
self.migong[self.y1][self.x1+1]=1
self.x1+=1
if self.y1==1 and self.x1==3:
self.end_game=1
r=-1
elif self.y1==3 and self.x1==1:
self.end_game=1
r=-1
elif self.y1==4 and self.x1==4:
self.end_game=2
r=1
#return self.migong
return self.end_game,r,self.migong


# 建立神经网络

[[[1,0,0,0,0],
[0,0,0,0,0],
[0,0,0,0,0],
[0,0,0,0,0],
[0,0,0,0,0],],

[[0,0,0,0,0],
[0,0,0,1,0],
[0,0,0,0,0],
[0,1,0,0,0],
[0,0,0,0,0],],

[[0,0,0,0,0],
[0,0,0,0,0],
[0,0,0,0,0],
[0,0,0,0,0],
[0,0,0,0,1],]]

def trans_torch(list1):
list1=np.array(list1)
l1=np.where(list1==1,1,0)
l2=np.where(list1==2,1,0)
l3=np.where(list1==3,1,0)
b=np.array([l1,l2,l3])
return b


class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.c1=nn.Conv2d(3,25,5,1,0)
self.f1=nn.Linear(25,16)
self.f1.weight.data.normal_(0, 0.1)
self.f2=nn.Linear(16,4)
self.f2.weight.data.normal_(0, 0.1)

def forward(self, x):
x=self.c1(x)
x=F.relu(x)
x = x.view(x.size(0),-1)
x=self.f1(x)
x=F.relu(x)
action=self.f2(x)
return action


# DQN

BATCH_SIZE = 32
LR = 0.01                   # 学习率
EPSILON = 0.9               # 最优选择动作百分比(有0.9的几率是最大选择，还有0.1是随机选择，增加网络能学到的Q值)
GAMMA = 0.9                 # 奖励递减参数（衰减作用，如果没有奖励值r=0，则衰减Q值）
TARGET_REPLACE_ITER = 100   # Q 现实网络的更新频率100次循环更新一次
MEMORY_CAPACITY = 2000      # 记忆库大小

N_ACTIONS = 4  # 棋子的动作0，1，2，3
N_STATES = 1


DQN框架:

class DQN(object):
def __init__(self):
self.eval_net, self.target_net = Net(), Net() #DQN需要使用两个神经网络
#eval为Q估计神经网络 target为Q现实神经网络
self.learn_step_counter = 0 # 用于 target 更新计时，100次更新一次
self.memory_counter = 0 # 记忆库记数
self.memory = list(np.zeros((MEMORY_CAPACITY, 4))) # 初始化记忆库用numpy生成一个(2000,4)大小的全0矩阵，
self.optimizer = torch.optim.Adam(self.eval_net.parameters(), lr=LR) # torch 的优化器
self.loss_func = nn.MSELoss()   # 误差公式
def choose_action(self, x):
x = torch.unsqueeze(torch.FloatTensor(x), 0)
# 这里只输入一个 sample,x为场景
if np.random.uniform() < EPSILON:   # 选最优动作
actions_value = self.eval_net.forward(x) #将场景输入Q估计神经网络
#torch.max(input,dim)返回dim最大值并且在第二个位置返回位置比如(tensor([0.6507]), tensor([2]))
action = torch.max(actions_value, 1)[1].data.numpy() # 返回动作最大值
else:   # 选随机动作
action = np.array([np.random.randint(0, N_ACTIONS)]) # 比如np.random.randint(0,2)是选择1或0
return action
def store_transition(self, s, a, r, s_):
# 如果记忆库满了, 就覆盖老数据，2000次覆盖一次
index = self.memory_counter % MEMORY_CAPACITY
self.memory[index] = [s,a,r,s_]
self.memory_counter += 1
def learn(self):
# target net 参数更新,每100次
if self.learn_step_counter % TARGET_REPLACE_ITER == 0:
# 将所有的eval_net里面的参数复制到target_net里面
self.learn_step_counter += 1
# 抽取记忆库中的批数据
# 从2000以内选择32个数据标签
sample_index = np.random.choice(MEMORY_CAPACITY, BATCH_SIZE)
b_s=[]
b_a=[]
b_r=[]
b_s_=[]
for i in sample_index:

b_s.append(self.memory[i][0])
b_a.append(np.array(self.memory[i][1],dtype=np.int32))
b_r.append(np.array([self.memory[i][2]],dtype=np.int32))
b_s_.append(self.memory[i][3])

b_s = torch.FloatTensor(b_s)#取出s
b_a = torch.LongTensor(b_a) #取出a
b_r = torch.FloatTensor(b_r) #取出r
b_s_ = torch.FloatTensor(b_s_) #取出s_
# 针对做过的动作b_a, 来选 q_eval 的值, (q_eval 原本有所有动作的值)
q_eval = self.eval_net(b_s).gather(1, b_a)  # shape (batch, 1) 找到action的Q估计(关于gather使用下面有介绍)
q_next = self.target_net(b_s_).detach()     # q_next 不进行反向传递误差, 所以 detach Q现实
q_target = b_r + GAMMA * q_next.max(1)[0]   # shape (batch, 1) DQL核心公式
loss = self.loss_func(q_eval, q_target) #计算误差
# 计算, 更新 eval net
loss.backward() #反向传递
self.optimizer.step()


# 训练部分

dqn = DQN() # 定义 DQN 系统
study=1
win=0
lose=0
env=Env()
for i_episode in range(200):
#print(i_episode,'epoch')
s = env.start_env()
s=trans_torch(s)
while True:
env.display()   # 显示实验动画
a = dqn.choose_action(s) #选择动作
# 选动作, 得到环境反馈
done,r,s_ = env.step(a)

s_=trans_torch(s_)
# 存记忆
dqn.store_transition(s, a, r, s_)
if dqn.memory_counter > MEMORY_CAPACITY:
if study==1:
print('2000经验池')
win=0
lose=0
study=0
dqn.learn() # 记忆库满了就进行学习
if done==1 or done==2:    # 如果回合结束, 进入下回合
#print(loss1)
if done==1:
lose+=1
print('epoch',i_episode,r,'失败','总计:胜利',win,'失败',lose)
if done==2:
win+=1
print('epoch',i_episode,r,'成功','总计:胜利',win,'失败',lose)
break
s = s_


# 训练程序总代码

train.py

from game import Env
import time
import random
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
#参数
BATCH_SIZE = 32
LR = 0.01                   # 学习率
EPSILON = 0.9               # 最优选择动作百分比(有0.9的几率是最大选择，还有0.1是随机选择，增加网络能学到的Q值)
GAMMA = 0.9                 # 奖励递减参数（衰减作用，如果没有奖励值r=0，则衰减Q值）
TARGET_REPLACE_ITER = 100   # Q 现实网络的更新频率100次循环更新一次
MEMORY_CAPACITY = 2000      # 记忆库大小
N_ACTIONS = 4  # 棋子的动作0，1，2，3
N_STATES = 1
def trans_torch(list1):
list1=np.array(list1)
l1=np.where(list1==1,1,0)
l2=np.where(list1==2,1,0)
l3=np.where(list1==3,1,0)
b=np.array([l1,l2,l3])
return b
#神经网络
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.c1=nn.Conv2d(3,25,5,1,0)
self.f1=nn.Linear(25,16)
self.f1.weight.data.normal_(0, 0.1)
self.f2=nn.Linear(16,4)
self.f2.weight.data.normal_(0, 0.1)
def forward(self, x):
x=self.c1(x)
x=F.relu(x)
x = x.view(x.size(0),-1)
x=self.f1(x)
x=F.relu(x)
action=self.f2(x)
return action
class DQN(object):
def __init__(self):
self.eval_net, self.target_net = Net(), Net() #DQN需要使用两个神经网络
#eval为Q估计神经网络 target为Q现实神经网络
self.learn_step_counter = 0 # 用于 target 更新计时，100次更新一次
self.memory_counter = 0 # 记忆库记数
self.memory = list(np.zeros((MEMORY_CAPACITY, 4))) # 初始化记忆库用numpy生成一个(2000,4)大小的全0矩阵，
self.optimizer = torch.optim.Adam(self.eval_net.parameters(), lr=LR) # torch 的优化器
self.loss_func = nn.MSELoss()   # 误差公式
def choose_action(self, x):
x = torch.unsqueeze(torch.FloatTensor(x), 0)
# 这里只输入一个 sample,x为场景
if np.random.uniform() < EPSILON:   # 选最优动作
actions_value = self.eval_net.forward(x) #将场景输入Q估计神经网络
#torch.max(input,dim)返回dim最大值并且在第二个位置返回位置比如(tensor([0.6507]), tensor([2]))
action = torch.max(actions_value, 1)[1].data.numpy() # 返回动作最大值
else:   # 选随机动作
action = np.array([np.random.randint(0, N_ACTIONS)]) # 比如np.random.randint(0,2)是选择1或0
return action
def store_transition(self, s, a, r, s_):
# 如果记忆库满了, 就覆盖老数据，2000次覆盖一次
index = self.memory_counter % MEMORY_CAPACITY
self.memory[index] = [s,a,r,s_]
self.memory_counter += 1
def learn(self):
# target net 参数更新,每100次
if self.learn_step_counter % TARGET_REPLACE_ITER == 0:
# 将所有的eval_net里面的参数复制到target_net里面
self.learn_step_counter += 1
# 抽取记忆库中的批数据
# 从2000以内选择32个数据标签
sample_index = np.random.choice(MEMORY_CAPACITY, BATCH_SIZE)
b_s=[]
b_a=[]
b_r=[]
b_s_=[]
for i in sample_index:
b_s.append(self.memory[i][0])
b_a.append(np.array(self.memory[i][1],dtype=np.int32))
b_r.append(np.array([self.memory[i][2]],dtype=np.int32))
b_s_.append(self.memory[i][3])
b_s = torch.FloatTensor(b_s)#取出s
b_a = torch.LongTensor(b_a) #取出a
b_r = torch.FloatTensor(b_r) #取出r
b_s_ = torch.FloatTensor(b_s_) #取出s_
# 针对做过的动作b_a, 来选 q_eval 的值, (q_eval 原本有所有动作的值)
q_eval = self.eval_net(b_s).gather(1, b_a)  # shape (batch, 1) 找到action的Q估计(关于gather使用下面有介绍)
q_next = self.target_net(b_s_).detach()     # q_next 不进行反向传递误差, 所以 detach Q现实
q_target = b_r + GAMMA * q_next.max(1)[0]   # shape (batch, 1) DQL核心公式
loss = self.loss_func(q_eval, q_target) #计算误差
# 计算, 更新 eval net
loss.backward() #反向传递
self.optimizer.step()
dqn = DQN() # 定义 DQN 系统
#400步
study=1
env=Env()
for i_episode in range(100):
#print(i_episode,'epoch')
s = env.start_env()
s=trans_torch(s)
while True:
env.display()   # 显示实验动画
a = dqn.choose_action(s) #选择动作
# 选动作, 得到环境反馈
done,r,s_ = env.step(a)
s_=trans_torch(s_)
# 存记忆
dqn.store_transition(s, a, r, s_)
if dqn.memory_counter > MEMORY_CAPACITY:
if study==1:
print('2000经验池')
study=0
dqn.learn() # 记忆库满了就进行学习
if done==1 or done==2:    # 如果回合结束, 进入下回合
#print(loss1)
if done==1:
print('epoch',i_episode,r,'失败')
if done==2:
print('epoch',i_episode,r,'成功')
break
s = s_


# 训练结果：

©️2019 CSDN 皮肤主题: 数字20 设计师: CSDN官方博客