利用人工智能玩智龙迷城,神魔之塔,转珠游戏(二)DQN

前言

我们知道了使用q-learning可以解决延迟奖励问题,但是还是有一个问题,他的表格太大了
所以我们使用神经网络来代替q表格,就是DQN啦

DQN

我们先设定一堆超参数(实际上跑一遍代码就知道他们分别有什么作用了):

# 超参数
BATCH_SIZE = 8              #一次抽8个batch训练
LR = 0.005                   # learning rate
EPSILON = 0.9               # 就是0.1的几率瞎选
GAMMA = 0.9                 # 奖励递减参数
TARGET_REPLACE_ITER = 100   # Q 现实网络的更新频率
MEMORY_CAPACITY = 2000      # 记忆库大小
#我们的转珠模拟结果
def pazzuleGame(x,y):
	return puzzle.add(x,y)

然后搭个网络用于预测:

class Net(nn.Module):
    def __init__(self,):
        super(Net, self).__init__()
        self.fc1 = nn.Linear(N_STATES, N_STATES*4)
        …………
        self.out4 = nn.Linear(N_STATES*16, N_ACTIONS)
        self.out4.weight.data.normal_(0, 0.1)   # initialization

        
    def forward(self, x):
        x = self.fc1(x)
        x = F.tanh(x)
       …………
        actions_value = self.out4(x)
        return actions_value

然后用目标网络和现实网络组合起来搭建DQN网络
这个网络的作用就是存储经验,然后通过经验训练预测网络。
然后用预测网络获取的值,和现实网络获取的值做loss
然后用loss去反向更新网络(参考上一章的神经网络小列)
然后更新一段时间后,再把预测网络 拷给 现实网络

上面看不懂 我就举个例子:
1 1 ② →→→→→→→→→→ → 1 2 3
2 2 3 →我转了一步后变成了→ 2 2 ②
3 3 4 →→→→→→→→→→→ 3 3 4

现实网络在左边的版面,预测往下走 不会得分 所以 Q现实= 0
但是实际上往下走得了1combo
所以Q实际 = 1分 + 0
Q现实和Q实际的误差就有了,然后后面做的就是缩小这个误差
(0*不是真的0,是GAMMA乘以当前网络后续可以取得的最大值)

class DQN(object):
    def __init__(self):
        self.eval_net, self.target_net = Net().to(device), Net().to(device)
        self.learn_step_counter = 0     # 用于 target 更新计时
        self.memory_counter = 0         # 记忆库记数
        self.memory = np.zeros((MEMORY_CAPACITY, N_STATES * 2 + 2))     # 初始化记忆库
        self.optimizer = torch.optim.Adam(self.eval_net.parameters(), lr=LR)    # torch 的优化器
        self.loss_func = nn.MSELoss().to(device)   # 误差公式
        
    def choose_action(self, x,limit):
        x = torch.unsqueeze(torch.FloatTensor(x).to(device), 0)
        # 这里只输入一个 sample
        if np.random.uniform() < EPSILON:   # 选最优动作
            actions_value = self.eval_net.forward(x)[0].data.numpy()
            if(len(limit)>0):
                for index,__ in enumerate(actions_value):
                    if(not index in limit):
                        actions_value[index] = -1e9
            #选一个最大的动作
            action = actions_value.argmax()
        else:   # 选随机动作
            if(len(limit)>0):
                action = np.random.choice(limit,1,False)[0]
            else:
                action = np.random.randint(0, N_ACTIONS)
        return action

    def store_transition(self, s, a, r, s_):
        transition = np.hstack((s, [a, r], s_))
        # 如果记忆库满了, 就覆盖老数据
        index = self.memory_counter % MEMORY_CAPACITY
        self.memory[index, :] = transition
        self.memory_counter += 1

    def learn(self):
        # target net 参数更新
        if self.learn_step_counter % TARGET_REPLACE_ITER == 0:
            self.target_net.load_state_dict(self.eval_net.state_dict())
        self.learn_step_counter += 1

        # 抽取记忆库中的批数据
        sample_index = np.random.choice(MEMORY_CAPACITY, BATCH_SIZE)
        b_memory = self.memory[sample_index, :]
        b_s = torch.FloatTensor(b_memory[:, :N_STATES])
        b_a = torch.LongTensor(b_memory[:, N_STATES:N_STATES+1].astype(int))
        b_r = torch.FloatTensor(b_memory[:, N_STATES+1:N_STATES+2])
        b_s_ = torch.FloatTensor(b_memory[:, -N_STATES:])

        # 针对做过的动作b_a, 来选 q_eval 的值, (q_eval 原本有所有动作的值)
        q_eval = self.eval_net(b_s).gather(1, b_a)  # shape (batch, 1)
        q_next = self.target_net(b_s_).detach()     # q_next 不进行反向传递误差, 所以 detach
        q_target = b_r + GAMMA * q_next.max(1)[0].view(BATCH_SIZE, 1)    # shape (batch, 1)
        loss = self.loss_func(q_eval, q_target)
        
        # 计算, 更新 eval net
        self.optimizer.zero_grad()
        loss.backward()
        self.optimizer.step()
        return loss

(可选)搭建一个可视化环境
可以用pygame和pyglet,前者简单一些,这里用的pygame
就是训练的时候把参数穿进去能看到系统选择

class Env:
    def __init__(self, row=6, col=5, fps=-1):
        self.rowSize = row
        self.colSize = col
        # 刷新率 -1不显示
        self.fps = fps
        # 三消
        self.thres = 3
        #self.board = np.array(row, col)
        # pygame
        self.board = np.random.randint(1,6,(5,6))
        pygame.init()
        self.screen = pygame.display.set_mode((135*row, 135*col))
        pygame.display.set_caption("PadAutomation")
        background = pygame.image.load(r'E:\RLpaz\data\normal.png').convert()
        red = pygame.image.load(r'E:\RLpaz\data\red.png').convert()
        green = pygame.image.load(r'E:\RLpaz\data\green.png').convert()
        yellow = pygame.image.load(r'E:\RLpaz\data\yellow.png').convert()
        dark = pygame.image.load(r'E:\RLpaz\data\dark.png').convert()
        blue = pygame.image.load(r'E:\RLpaz\data\blue.png').convert()
        pink = pygame.image.load(r'E:\RLpaz\data\pink.png').convert()
        self.switch = {1: red,
                       2: blue,
                       3: green,
                       4: yellow,
                       5: dark,
                       6: pink,
                       }

    def step(self,pos):
        self.screen.fill(0)
        for i in range(5):
            for j in range(6):
                photo = self.switch[pos[i][j]]
                self.screen.blit(photo, (i*135, j*135))
        pygame.display.update()

    def getscreen(self,):
        return self.screen

    def gameStart(self,fps=2):
        if(fps<0):
            return
        fcclock = pygame.time.Clock()
        while True:
            if(fps!=0):
                fcclock.tick(fps)
            self.step(self.board)
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    pygame.quit()
    def setBoard(self,board):
        self.board = board

网络搭好了,剩下就是弄一个模拟游戏环境。
这一部分和前面一样就不重复了
主要是要多设计一个算分逻辑
怎么设计都行,目的是为了增加奖惩,训练神经网络收敛

#算分
    def score(self,curcombo=0):
        tmp = deepcopy(self.board)
        maxcombo = self.maxcomb
        combo = self.calculate()
        self.board = tmp
        
        if(combo == curcombo):
            return 0,combo
        if(combo < curcombo):
            return -pow(2,curcombo*2-combo),combo
        #分数计算规则
        reward = pow(2,combo)
        if(combo ==maxcombo):
            reward = pow(2,maxcombo+2)
        return reward,combo

其他技巧

独热编码 ont-hot encoding

说白了就是把单维度的特征向量分离出来
比如珠子的颜色是红色,蓝色、 不能单纯的把它们标注成1、2
而是用两种维度,比如 是红色:1 是蓝色: 0 这样
虽然增加了观测数据维度,但是解决了数据属性问题

GPU加速

pytorch可以利用cuda加速tensor(张量)计算
神经网络内部传播都是基于张量,所以可以用.to(‘cuda’)的方式将张量置于gpu中
注:取出来一定要转换成.cpu() 不然会报错
如 :

#判断有没有GPU
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
#使用举例:
self.eval_net, self.target_net = Net().to(device), Net().to(device)
self.loss_func = nn.MSELoss().to(device)

#取出来
actions_value = self.eval_net.forward(x)[0].cpu().data.numpy()

*(可选优化)*版面转换

因为
1 1
2 2

2 2
1 1
这样的输入实际上没有本质区别,可以通过手动转换把数据维度缩减
6个颜色的珠子就可以缩减C62倍的训练量
实现方法怎样都可以,在训练的时候转换成输入就行了

 def boardTrans(self,s):
        record = []
        pos= []
        for i in s:
            if(not i in record):
                record.append(i)
                pos.append(np.where(s==i))
                if(len(record)==self.colorSize):
                    break
        index = 0
        res = np.zeros(s.shape[0])
        for i in pos:
            index+=1
            for j in i:
                res[j]=index
        return res

然后就是重复路径筛选,我们知道转的时候不会走回头路,这就是一个可以优化的点……之类的

然后就可以开始跑训练啦
利用人工智能玩智龙迷城,神魔之塔,转珠游戏(二)DQN_第1张图片
42万epochs后:
利用人工智能玩智龙迷城,神魔之塔,转珠游戏(二)DQN_第2张图片

但是转珠游戏的话DQN还是不太适合
因为输入的是整个环境,环境特征过多 难以收敛

如果只输入周围珠子,反而会陷入局部最优

你可能感兴趣的:(DQN,强化学习,python)