nuaa-数据融合-基于强化学习的小游戏

目录

一、写在前面

二、安装pygame

 三、读整个项目文件中的README.md

四、模拟强化学习(重点)

4.1先装cuda

4.2 再装cuDNN

4.3 添加环境变量

五、使用conda下载pytorch

反转来了

 env.py

main.py

ppo.py


一、写在前面

首先到github上下载这个项目

  • GitHub - PiperLiu/Amazing-Brick-DFS-and-DRL: 用 深度优先搜索 DFS 与 深度强化学习 DRL 分别自动控制 amazing brick 小游戏

然后先大概浏览下项目的框架1.5万字详述 | 全开源:python写小游戏+AI强化学习与传统DFS/BFS控制分别实现_强化学习python小游戏_枇杷鹭的博客-CSDN博客

二、安装pygame

1.在anconda环境中安装这个包 

nuaa-数据融合-基于强化学习的小游戏_第1张图片

2.安装pygame :pip install pygame -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com

缘由:pip安装包报错Could not find a version that satisfies the requirement pymysql (from versions: none) - 知乎

nuaa-数据融合-基于强化学习的小游戏_第2张图片

 三、读整个项目文件中的README.md

然后模拟DFS和BFS

四、模拟强化学习(重点)

4.1先装cuda

深度学习环境搭建(GPU)CUDA安装(完全版)_cuda_10.2.89_441.22_win10_小邢同学的博客-CSDN博客

 看看自己的版本 比如我这里是12.1.68 那我下载12.1.1的cuda应该是没问题的

去CUDA Toolkit Archive | NVIDIA Developernuaa-数据融合-基于强化学习的小游戏_第3张图片

 选择版本 然后下载 (强推IDM下载器)

nuaa-数据融合-基于强化学习的小游戏_第4张图片

 然后运行下载的exe (下图是10.2的版本 实际我这里是我刚说的12.1)

nuaa-数据融合-基于强化学习的小游戏_第5张图片

 检查通过了以后选自定义

nuaa-数据融合-基于强化学习的小游戏_第6张图片

 nuaa-数据融合-基于强化学习的小游戏_第7张图片

 nuaa-数据融合-基于强化学习的小游戏_第8张图片

 等待安装完成关闭就行了,记得安装过程不要打开vs

这个时候cmd就已经能够查到cuda的版本了 nvcc   -V 

nuaa-数据融合-基于强化学习的小游戏_第9张图片

4.2 再装cuDNN

来到官网

lCUDA Deep Neural Network (cuDNN) | NVIDIA Developer

nuaa-数据融合-基于强化学习的小游戏_第10张图片

 用谷歌邮箱或者什么玩意登录一下或者注册看下面的链接【NVIDIA】账户该如何创建-百度经验nuaa-数据融合-基于强化学习的小游戏_第11张图片

终于竟来​​​​​进来了nuaa-数据融合-基于强化学习的小游戏_第12张图片

nuaa-数据融合-基于强化学习的小游戏_第13张图片

 解压后的三个文件夹和一个TXT文档放到刚才CUDA的安装目录下 全部替换

nuaa-数据融合-基于强化学习的小游戏_第14张图片

nuaa-数据融合-基于强化学习的小游戏_第15张图片

4.3 添加环境变量

可以发现已经有了两个环境变量

nuaa-数据融合-基于强化学习的小游戏_第16张图片

 这个时候cmd就已经能够查到cuda的版本了 nvcc   -V 

nuaa-数据融合-基于强化学习的小游戏_第17张图片

五、使用conda下载pytorch

看一下自己py版本

 创建一个虚拟环境nuaa-数据融合-基于强化学习的小游戏_第18张图片

 conda create –n 虚拟环境名字 python=版本 如果发生下面的报错就 先恢复下默认的源

conda config --remove-key channels

 nuaa-数据融合-基于强化学习的小游戏_第19张图片

 细节:Anaconda创建虚拟环境报错—UnavailableInvalidChannel: The channel is not accessible or is invalid_wt-cai的博客-CSDN博客nuaa-数据融合-基于强化学习的小游戏_第20张图片

 验证是否成功 conda info --envs

  nuaa-数据融合-基于强化学习的小游戏_第21张图片

 进入虚拟环境

进入官网看选择合适的CUDA版本  (解释:2023最新pytorch安装教程,简单易懂,面向初学者(Anaconda+GPU)_时宇羽然的博客-CSDN博客)

 Start Locally | PyTorchnuaa-数据融合-基于强化学习的小游戏_第22张图片

 然后在刚才的虚拟环境中下载  (注意如果这个时候有源(最好抹除掉再执行命令))

conda install pytorch torchvision torchaudio pytorch-cuda=11.8 -c pytorch -c nvidia

nuaa-数据融合-基于强化学习的小游戏_第23张图片

漫长的等待后co 

验证下: conda list  看看有没有pytorch或者torch 有就代表成功 (在虚拟环境中)

另外:

nuaa-数据融合-基于强化学习的小游戏_第24张图片

nuaa-数据融合-基于强化学习的小游戏_第25张图片

 要在虚拟环境中才行(不然就是false)

nuaa-数据融合-基于强化学习的小游戏_第26张图片

然后就可以按照上面的博客运行你的代码了。

反转来了

由于我和队友都请假了,结果上课傻眼了,有一组也是用的这个,所以我们只能重新做。

基于强化学习PPO的扫雷游戏

我们参考的是:强化学习:基于pygame和pytorch实现ppo算法在扫雷小游戏上的AI决策_pygame 强化学习_最爱小游侠的博客-CSDN博客

一些可能会帮助的链接:Proximal Policy Optimization (PPO) 算法理解:从策略梯度开始 - 知乎

放源码:

nuaa-数据融合-基于强化学习的小游戏_第27张图片

 env.py

import pygame
import random
import os
import sys
import time
import numpy as np
import torch

pygame.init()

class Minesweeper:
    def __init__(self,grid_width=10,grid_height=10,cell_size=50,mine_count=13,window=True):
    #设置游戏窗口的大小、标题和背景色。
    #初始化游戏所需的变量,包括地雷数量、格子大小、游戏状态等

        # 定义常量
        self.GRID_WIDTH = grid_width  # 游戏网格宽度
        self.GRID_HEIGHT = grid_height  # 游戏网格高度
        self.CELL_SIZE = cell_size  # 单元格尺寸
        self.MINE_COUNT = mine_count  # 雷的数量

        self.RED = (255, 0, 0)  # 炸弹颜色,红色
        self.WHITE = (255, 255, 255)  # 白色底色
        self.BLACK = (0, 0, 0)  # 黑色,表示未翻开的方块色
        self.GREY = (128, 128, 128)  # 灰色,表示翻开后的方块颜色

        self.font = pygame.font.SysFont(None, 30)  # 设置字体
        self.window = window
        self.akc=False
        if self.window:
            pygame.display.set_caption("Minesweeper")  # 设置窗口标题
            self.screen = pygame.display.set_mode((self.GRID_WIDTH * self.CELL_SIZE, self.GRID_HEIGHT * self.CELL_SIZE))  # 设置可视化窗口大小

            self.r = 0.
            self.R = []
            self.actions = []
            self.condition = True
            self.map = np.zeros([self.GRID_WIDTH, self.GRID_HEIGHT])
            self.t = 0
            self.count = np.zeros([self.GRID_WIDTH, self.GRID_HEIGHT])

        else:
            self.r=0.
            self.R=[]
            self.actions=[]
            self.condition=True
            self.map=np.zeros([self.GRID_WIDTH,self.GRID_HEIGHT])
            self.t = 0
            self.count=np.zeros([self.GRID_WIDTH,self.GRID_HEIGHT])

        self.grid = [[0 for _ in range(self.GRID_HEIGHT)] for _ in range(self.GRID_WIDTH)]  # 创建二维数组,0表示无雷的安全区域
        self.mines = []  # 存储地雷的位置
        for i in range(self.MINE_COUNT):
            while True:
                x = random.randint(0, self.GRID_WIDTH - 1)  # 随机生成x坐标
                y = random.randint(0, self.GRID_HEIGHT - 1)  # 随机生成y坐标
                if (x, y) not in self.mines:  # 如果该位置没有地雷
                    self.mines.append((x, y))  # 将该位置添加到地雷列表中
                    self.grid[x][y] = -1  # 在该位置标记为地雷
                    break

        self.revealed = np.array([[False for _ in range(self.GRID_HEIGHT)] for _ in range(self.GRID_WIDTH)])  # 创建未翻开的方块信息
        if not self.window:
            self.status=self.get_status()
        else:
            self.status = self.get_status()

    def get_adjacent_cells(self, x, y): # 类的子函数 get_status的功能为获取环境当前状态,状态为当前游戏中每个格子的信息(未揭示为1)和点击次数,用作为智能体的输入信息,其返回参数为状态信息status
        '''获取目标位置的相邻元素格'''
        cells = []
        for i in range(max(0, x - 1), min(x + 2, self.GRID_WIDTH)):
            for j in range(max(0, y - 1), min(y + 2, self.GRID_HEIGHT)):
                if i != x or j != y:
                    cells.append((i, j))
        return cells

    def get_status(self):
        ''' 用于获取环境的当前状态,返回一个包含未翻开的方块信息和雷的数量的数组。'''
        status = (self.revealed.astype(np.float64) - 1) + self.map
        status = np.stack((status,self.count),axis=0)
        return status

    def reveal_cell(self, x, y):
        '''揭示指定位置的格子'''
        self.revealed[x][y] = True # 标记该位置已揭示
        if self.window:
            rect = pygame.Rect(x * self.CELL_SIZE, y * self.CELL_SIZE, self.CELL_SIZE, self.CELL_SIZE) # 创建矩形
            pygame.draw.rect(self.screen, self.WHITE, rect) # 在该位置绘制白色矩形
            if self.grid[x][y] == -1: # 如果该位置是地雷
                self.map[x, y] = -10
                pygame.draw.circle(self.screen, self.RED, rect.center, self.CELL_SIZE // 3) # 在该位置绘制红色圆形
            else:
                pygame.draw.rect(self.screen, self.GREY, rect) # 在该位置绘制灰色矩形
                self.map[x, y] = self.count_adjacent_mines(x, y)
                if self.count_adjacent_mines(x, y) > 0: # 如果该位置周围有地雷
                    text = self.font.render(str(self.count_adjacent_mines(x, y)), True, self.BLACK) # 创建文本
                    text_rect = text.get_rect(center=rect.center) # 设置文本位置
                    self.screen.blit(text, text_rect) # 在该位置绘制文本
        else:
            if self.grid[x][y] == -1:
                self.map[x,y]=-10
            else:
                self.map[x,y]=self.count_adjacent_mines(x, y)

    def reveal_all_cells(self):
        '''将所有未揭示的格子都揭示出来'''
        for i in range(self.GRID_WIDTH):
            for j in range(self.GRID_HEIGHT):
                if not self.revealed[i][j]:
                    self.reveal_cell(i, j)

    def agent_click(self,x,y): #该函数在与智能体交互时使用
        '''智能体点击指定位置的格子'''
        if self.revealed[x][y]:
            # self.r+=-(100.+4*self.t)/100.
            self.r += 0.
        elif self.grid[x][y] != -1:
            self.reveal_cell(x,y)
            self.r=1.
            # self.r += (1.+2*self.revealed.sum()/(self.GRID_WIDTH*self.GRID_HEIGHT))
            if self.count_adjacent_mines(x, y) == 0: # 如果该位置周围没有地雷
                for i, j in self.get_adjacent_cells(x, y):
                    if self.grid[i][j] != -1 and not self.revealed[i][j]:
                        self.agent_click(i, j) # 递归揭示周围的位置
        else:
            self.reveal_all_cells()
            # self.r+=-10.
            self.r += 0.
            self.condition=False

    def handle_left_click(self, x, y): #具体功能与函数agent_click类似,最不同的地方是当点击到雷时会重置游戏。
        if self.revealed[x][y]:
            # self.r+=-(100.+4*self.t)/100.
            self.r += 0.
        elif self.grid[x][y] != -1: # 如果该位置不是地雷
            self.reveal_cell(x, y) # 揭示该位置
            self.r = 1.
            if self.count_adjacent_mines(x, y) == 0: # 如果该位置周围没有地雷
                for i, j in self.get_adjacent_cells(x, y):
                    if self.grid[i][j] != -1 and not self.revealed[i][j]:
                        self.handle_left_click(i, j) # 递归揭示周围的位置
        else:
            self.reveal_all_cells() # 揭示所有位置
            self.r += 0.
            self.condition = False
            pygame.display.flip()
            if self.akc:
                # self.running=False

                time.sleep(2.)
                self.reset()

    def handle_right_click(self, x, y):
        pass

    def draw_grid(self):
        '''用于绘制游戏窗口中的网格'''
        for i in range(self.GRID_WIDTH):
            for j in range(self.GRID_HEIGHT):
                rect = pygame.Rect(i * self.CELL_SIZE, j * self.CELL_SIZE, self.CELL_SIZE, self.CELL_SIZE) # 创建矩形
                pygame.draw.rect(self.screen, self.WHITE, rect, 1) # 在该位置绘制白色矩形
                if self.revealed[i][j]: # 如果该位置已揭示
                    if self.grid[i][j] == -1: # 如果该位置是地雷
                        pygame.draw.circle(self.screen, self.RED, rect.center, self.CELL_SIZE // 3) # 在该位置绘制红色圆形
                    else:
                        pygame.draw.rect(self.screen, self.GREY, rect) # 在该位置绘制灰色矩形
                        if self.count_adjacent_mines(i, j) > 0: # 如果该位置周围有地雷
                            text = self.font.render(str(self.count_adjacent_mines(i, j)), True, self.BLACK) # 创建文本
                            text_rect = text.get_rect(center=rect.center) # 设置文本位置
                            self.screen.blit(text, text_rect) # 在该位置绘制文本

    def count_adjacent_mines(self, x, y):
        '''计数周围雷的数量'''
        count = 0
        for i, j in self.get_adjacent_cells(x, y):  # 循环变量周围的方块的坐标信息
            if self.grid[i][j] == -1:
                count += 1
        return count

    def reset(self):
        self.grid = [[0 for _ in range(self.GRID_HEIGHT)] for _ in range(self.GRID_WIDTH)]  # 创建二维数组,0表示无雷的安全区域
        self.mines = []  # 存储地雷的位置
        for i in range(self.MINE_COUNT):
            while True:
                x = random.randint(0, self.GRID_WIDTH - 1)  # 随机生成x坐标
                y = random.randint(0, self.GRID_HEIGHT - 1)  # 随机生成y坐标
                if (x, y) not in self.mines:  # 如果该位置没有地雷
                    self.mines.append((x, y))  # 将该位置添加到地雷列表中
                    self.grid[x][y] = -1  # 在该位置标记为地雷
                    break

        self.revealed = np.array(
            [[False for _ in range(self.GRID_HEIGHT)] for _ in range(self.GRID_WIDTH)])  # 创建未翻开的方块信息
        if not self.window:
            self.r = 0.
            self.R = []
            self.actions = []
            self.condition = True
            self.map = np.zeros([self.GRID_WIDTH, self.GRID_HEIGHT])
            self.status = self.get_status()
            self.t=0
            self.count=np.zeros([self.GRID_WIDTH,self.GRID_HEIGHT])
        else:
            self.r = 0.
            self.R = []
            self.actions = []
            self.condition = True
            self.map = np.zeros([self.GRID_WIDTH, self.GRID_HEIGHT])
            self.status = self.get_status()
            self.t = 0
            self.count = np.zeros([self.GRID_WIDTH, self.GRID_HEIGHT])

            # time.sleep(1.)
            self.screen = pygame.display.set_mode(
                (self.GRID_WIDTH * self.CELL_SIZE, self.GRID_HEIGHT * self.CELL_SIZE))  # 设置可视化窗口大小
            self.screen.fill(self.BLACK)  # 填充黑色
            self.draw_grid()
            pygame.display.flip()  # 更新屏幕
            # time.sleep(1.)


    def update(self,a):
        '''函数接受一个参数a,表示智能体选择的动作(格子的坐标),函数首先会揭示该位置(其它参数更新再次过程中进行),并更新点击状态,之后函数会判断游戏是否达到胜利条件,如果达到则返回高额奖励并更新游戏状态为False表示游戏结束,
        否则则判断交互次数是否达到阈值(50)次,如果达到则更新游戏状态为False表示游戏结束。最后函数返回给智能体游戏的状态信息,奖励,游戏状态等信息。
'''
        '''更新游戏状态并返回相应的结果'''
        [x,y]=a
        self.r=0.
        self.agent_click(x,y)
        self.count[x,y]+=1
        # if self.revealed.sum() == (self.GRID_WIDTH*self.GRID_HEIGHT-self.MINE_COUNT):
        #     self.r+=200.
        #     self.condition=False

        if self.revealed.sum() <=(self.GRID_WIDTH*self.GRID_HEIGHT-self.MINE_COUNT) and self.revealed.sum()>=(self.GRID_WIDTH*self.GRID_HEIGHT-self.MINE_COUNT-10) :
            self.r=50.
            self.condition=False

        self.status=self.get_status()
        self.R.append(self.r)
        self.actions.append([x,y])
        self.t+=1
        if self.t==50:
            self.condition=False
            # self.r=-20.
            self.r = 0.
        return [torch.tensor(self.status,dtype=torch.float32),self.r,self.condition]

    def agengt_run(self,a): #智能体与游戏的可视化交互,函数接收智能体的决策a,并更新视窗,具体功能与函数undate相似。
        [x,y]=a
        self.r = 0.
        self.handle_left_click(x,y)
        self.draw_grid()  # 绘制网格
        pygame.display.flip()  # 更新屏幕

        self.count[x, y] += 1
        if self.revealed.sum() <=(self.GRID_WIDTH*self.GRID_HEIGHT-self.MINE_COUNT) and self.revealed.sum()>=(self.GRID_WIDTH*self.GRID_HEIGHT-self.MINE_COUNT-10) :
            self.r=10.
            self.condition=False
        self.status = self.get_status()
        self.R.append(self.r)
        self.actions.append([x, y])
        self.t += 1
        if self.t==50:
            self.condition=False
            # self.r=-20.
            self.r = 0.
        return [torch.tensor(self.status,dtype=torch.float32),self.r,self.condition]

    def quit(self):
        # 退出pygame
        pygame.quit()

    def run(self):
        # 设置视频驱动为dummy
        # os.environ['SDL_VIDEODRIVER'] = 'dummy'
        self.akc=True

        # 主游戏循环
        self.running = True
        while self.running:
            # 处理事件
            for event in pygame.event.get():  # 当得到一个相应事件时
                if event.type == pygame.QUIT:  # 如果是退出事件(X按钮)
                    self.running = False
                elif event.type == pygame.MOUSEBUTTONDOWN:  # 如果是鼠标点击事件
                    x, y = event.pos # 得到鼠标在窗口中的位置
                    x //= self.CELL_SIZE  # 整除方块大小得到鼠标在具体哪个方块
                    y //= self.CELL_SIZE
                    if event.button == 1:
                        self.handle_left_click(x, y) # 处理左键点击事件
                    elif event.button == 3:
                        self.handle_right_click(x, y) # 处理右键点击事件

            # 绘制屏幕,刷新屏幕内容
            # self.screen.fill(self.BLACK) # 填充黑色
            self.draw_grid() # 绘制网格
            pygame.display.flip() # 更新屏幕

        # 退出pygame
        pygame.quit()
        # sys.exit()  # 退出Python程序

if __name__=='__main__':
    minesweeper = Minesweeper()
    minesweeper.run()

main.py

import torch
from torch.distributions.categorical import Categorical
import numpy as np
from collections import namedtuple
from tqdm import tqdm
from pyecharts.charts import Line
from env import Minesweeper
from ppo import PPO
import time


def get_a(a,x_idx,y_idx):
    if x_idx > y_idx:
        x = a // x_idx
        y = a % x_idx
    else:
        x = a // y_idx
        y = a % y_idx
    return [x, y]

def get_action_test(x1, net, x_idx, y_idx):
    x = x1.unsqueeze(dim=0)
    ac_prob = net(x)
    values, indices = ac_prob.topk(k=15,dim=1)
    a = Categorical(values).sample()[0]  # 按概率采样
    a = indices[0,a].item()
    ac_prob=ac_prob.detach().numpy()
    ac_prob=ac_prob.reshape([1,10,10])

    if x_idx > y_idx:
        x = a // x_idx
        y = a % x_idx
    else:
        x = a // y_idx
        y = a % y_idx
    return [x, y],ac_prob

def mian(times,x,y,mine_num):
    env=Minesweeper(grid_width=x,grid_height=y,mine_count=mine_num,window=False)
    net=PPO(input_shape=[x,y],up_time=up_time,batch_size=batch_size,a_lr=a_lr,b_lr=b_lr,gama=gama,epsilon=epsilon)
    # path='net_model1.pt'
    # net.load_net(path)
    Rs=[]
    for i in range(times):
        with tqdm(total=epoch, desc='Iteration %d' % i) as pbar:
            for e in range(epoch):
                env.reset()
                s=torch.tensor(env.get_status(),dtype=torch.float32)
                while env.condition and env.t<51:
                    a,a_p=net.get_action(s)
                    at=get_a(a[0],x,y)
                    [s_t,r,d]=env.update(at)
                    buffer=Transition(s,a,a_p,r,d)
                    net.appdend(buffer)
                    s=s_t
                R=np.array(env.R).sum()
                Rs.append(R)
                if len(net.suffer)>batch_size:
                    net.update()
                pbar.set_postfix({'return': '%.2f' % R})
                pbar.update(1)

    torch.save(net.action,'net_model.pt')
    Re=[]
    for i in range(int(len(Rs)/50)):
        idx=i*50
        Re.append(sum(Rs[idx:idx+50])/50)
    x=[str(i) for i in range(len(Re))]
    line=Line()
    line.add_xaxis(xaxis_data=x)
    line.add_yaxis(y_axis=Re,series_name='Recall')
    line.render('result.html')


def main_test(path, x=10, y=10, mine_num=10):
    env = Minesweeper(grid_width=x, grid_height=y, mine_count=mine_num)
    net = torch.load(path)
    device = torch.device("cpu")
    net = net.to(device)
    s = torch.tensor(env.get_status(), dtype=torch.float32)
    a_p = 0
    for i in range(10):
        while env.condition:
            a, a_p = get_action_test(s, net, x_idx=x, y_idx=y)
            [s_t, r, d] = env.agengt_run(a)
            time.sleep(1.)
            s = s_t
        env.reset()

batch_size=32
a_lr=0.0001
b_lr=0.002
gama=0.995
epsilon=0.2
up_time=10
epoch=50

Transition = namedtuple('Transition', ['state', 'ac', 'ac_prob', 'reward', 'done'])

if __name__=='__main__':
    #minesweeper = Minesweeper()
    #minesweeper.run()


     # mian(times=500,x=10,y=10,mine_num=10)

     path='net_model.pt'
     main_test(path=path, x=10, y=10, mine_num=10)

ppo.py

import torch
import torch.nn as nn
import torch.optim as optim
from torch.distributions.categorical import Categorical
import random

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(device)

class Action1(nn.Module): #根据当前状态输出一个动作概率分布
    #10x10的输入图像,经过一系列卷积层和非线性激活函数的处理后,输出一个概率分布,用于表示预测的动作。
    def __init__(self,input_shape=[10,10]): #

        super(Action1,self).__init__()
        self.input_dim=input_shape
        self.conv_layers = nn.Sequential(
            nn.Conv2d(in_channels=2, out_channels=32, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.Conv2d(in_channels=32, out_channels=128, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.Conv2d(in_channels=128, out_channels=32, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.Conv2d(in_channels=32, out_channels=1, kernel_size=3, stride=1, padding=1), #  第四个卷积层:输入通道数为32,输出通道数为1,卷积核大小为3x3,步长为1,填充为1。
        )
        self.softmax = nn.Softmax(dim=1)  #应用在卷积层输出上的维度1上
        self.relu = nn.ReLU() #另一个是ReLU函数,用于在模型的forward方法中使用。

    def forward(self,x):
        x=self.conv_layers(x).view(x.shape[0],-1)
        out = self.softmax(x)
        return out

class Action2(nn.Module): #Action2网络则通常采用全连接神经网络,输入为当前状态的特征向量,输出为一个动作概率分布向量
    def __init__(self,input_shape=[10,10]):
        super(Action2,self).__init__()
        self.input_dim=input_shape[0]*input_shape[1]
        self.output_dim=(input_shape[0]+6)*(input_shape[1]+6)
        self.liner=nn.Linear(self.input_dim,512) #第一个全连接层输入大小为输入特征向量的维度(input_dim),输出大小为512。
        self.liner2=nn.Linear(512,self.output_dim) #第二个全连接层:输入大小为512,输出大小为输出特征向量的维度(output_dim)。
        self.liner3 = nn.Linear(self.output_dim,self.input_dim) #输入大小为输出特征向量的维度(output_dim),输出大小为输入特征向量的维度(input_dim)。

        self.softmax = nn.Softmax(dim=1) #应用在第三个全连接层的输出上,用于产生输出的概率分布
        self.relu = nn.ReLU()

    def forward(self,x): #在forward方法中,输入x首先通过展平操作将其变成一维张量,然后经过第一个全连接层和ReLU激活函数的处理。
        # 接着,再经过第二个全连接层和ReLU激活函数的处理。最后,通过第三个全连接层和softmax激活函数的处理,得到输出的动作概率分布向量。
        x=x.view(x.shape[0],-1)
        x=self.relu(self.liner(x))
        x=self.relu(self.liner2(x))
        out=self.softmax(self.liner3(x))
        return out

class Bvalue(nn.Module): #Bvalue网络的作用是估计在当前状态下,采取某个动作所能获得的预期累积奖励。具体实现上,
        # Bvalue网络通常采用全连接神经网络,输入为当前状态的特征向量,输出为一个标量值,代表当前状态的价值。在PPO算法中,Bvalue网络的损失函数通常采用均方误差损失,目标值为当前状态下的实际累积奖励。
    def __init__(self):
        super(Bvalue,self).__init__()
        self.relu = nn.ReLU()
        self.liner=nn.Linear(200,256)
        self.liner2=nn.Linear(256,512)
        self.liner3 = nn.Linear(512,1) #线性层接收512维向量作为输入,并输出一个标量值,即当前状态的价值
        #损失函数采用均方误差损失,目标值则是当前状态下的实际累积奖励。

    def forward(self,x):
        x = x.view(x.shape[0], -1)
        x=self.relu(self.liner(x))
        x=self.relu(self.liner2(x))
        out = self.liner3(x)
        return out

class PPO():

    def __init__(self,input_shape=[10,10],up_time=10,batch_size=32,a_lr=1e-5,b_lr=1e-5,gama=0.9,epsilon=0.1):
        #初始化算法所需参数,以及两个神经网络模型 action 和 bvalue,并创建了对应的优化器和损失函数。
        self.up_time=up_time
        self.batch_size=batch_size
        self.gama=gama
        self.epsilon=epsilon
        self.suffer = []
        self.action = Action1(input_shape)
        self.action.to(device)
        self.bvalue = Bvalue()
        self.bvalue.to(device)
        self.acoptim = optim.Adam(self.action.parameters(), lr=a_lr)
        self.boptim = optim.Adam(self.bvalue.parameters(), lr=b_lr)
        self.loss = nn.MSELoss().to(device)
        self.old_prob = []

    def appdend(self, buffer):
        self.suffer.append(buffer)

    def load_net(self,path): #从预训练模型文件中加载 action 模型
        self.action=torch.load(path)

    def get_action(self, x): #接受一个状态 x,通过 action 模型计算出给定状态下各个动作的概率分布,并按照概率分布采样得到一个动作
        x = x.unsqueeze(dim=0).to(device)
        ac_prob = self.action(x)

        a = Categorical(ac_prob).sample()[0]  # 按概率采样

        # values, indices = ac_prob.topk(k=15,dim=1)
        # a = Categorical(values).sample()[0]  # 按topk15概率采样
        # a = indices[0,a]

        ac_pro = ac_prob[0][a]
        return [a.item()], [ac_pro.item()]

    def update(self): #接受一个状态 x,通过 action 模型计算出给定状态下各个动作的概率分布,并按照概率分布采样得到一个动作
        states = torch.stack([t.state for t in self.suffer],dim=0).to(device)
        actions = torch.tensor([t.ac for t in self.suffer], dtype=torch.int).to(device)
        rewards = [t.reward for t in self.suffer]
        done=[t.done for t in self.suffer]
        old_probs = torch.tensor([t.ac_prob for t in self.suffer], dtype=torch.float32).to(device)  # .detach()

        false_indexes = [i+1 for i, val in enumerate(done) if not val]
        if len(false_indexes)>=0:
            idx,reward_all=0,[]
            for i in false_indexes:
                reward=rewards[idx:i]
                R = 0
                Rs = []
                reward.reverse()
                for r in reward:
                    R = r + R * self.gama
                    Rs.append(R)
                Rs.reverse()
                reward_all.extend(Rs)
                idx=i
        else:
            R = 0
            reward_all = []
            rewards.reverse()
            for r in rewards:
                R = r + R * self.gama
                reward_all.append(R)
            reward_all.reverse()
        Rs = torch.tensor(reward_all, dtype=torch.float32).to(device)
        for _ in range(self.up_time):
            self.action.train()
            self.bvalue.train()
            for n in range(max(10, int(10 * len(self.suffer) / self.batch_size))):
                index = torch.tensor(random.sample(range(len(self.suffer)), self.batch_size), dtype=torch.int64).to(device)
                v_target = torch.index_select(Rs, dim=0, index=index).unsqueeze(dim=1)
                v = self.bvalue(torch.index_select(states, 0, index))
                adta = v_target - v
                adta = adta.detach() #adta表示在当前状态下执行动作所获得的实际奖励和当前状态价值函数的差值
                probs = self.action(torch.index_select(states, 0, index))
                pro_index = torch.index_select(actions,0,index).to(torch.int64)

                probs_a = torch.gather(probs, 1, pro_index)
                ratio = probs_a / torch.index_select(old_probs, 0, index).to(device)#ratio表示新旧策略之间的动作概率分布比值
                surr1 = ratio * adta
                surr2 = torch.clip(ratio, 1 - self.epsilon, 1 + self.epsilon) * adta.to(device) #self.epsilon是超参数,用于控制策略更新的幅度
                action_loss = -torch.mean(torch.minimum(surr1, surr2)) #通过比较surr1和surr2,选择其中更小的一个,作为策略梯度损失action_loss
                self.acoptim.zero_grad() #self.acoptim 表示Actor和Critic网络的优化器
                # 通过调用zero_grad()方法清除梯度,然后调用backward()方法计算梯度,最后调用step()方法更新网络参数
                action_loss.backward(retain_graph=True)
                self.acoptim.step()

                bvalue_loss = self.loss(v_target, v) #v_target表示当前状态下的实际累积奖励,v表示Critic网络对当前状态的估计价值。
                # bvalue_loss采用均方误差损失,用于衡量Critic网络的估计值和实际值之间的差距,并通过反向传播更新Critic网络的参数
                self.boptim.zero_grad() #self.boptim也表示Actor和Critic网络的优化器
                bvalue_loss.backward()
                #通过调用zero_grad()方法清除梯度,然后调用backward()方法计算梯度,最后调用step()方法更新网络参数

                self.boptim.step()
                #由于Actor网络和Critic网络是共享的,因此需要分别对两个网络的参数进行更新。
        self.suffer = []

最后放一些可能会帮助理解的图片:

nuaa-数据融合-基于强化学习的小游戏_第28张图片

 nuaa-数据融合-基于强化学习的小游戏_第29张图片

 

你可能感兴趣的:(机器学习)