用Python+PyTorch实现DQN无人机避障:从环境搭建到实战训练(附完整代码)
你是否曾想过,让一架无人机像拥有“直觉”一样,在复杂的室内或模拟环境中自主穿梭,灵巧地避开所有障碍物?这听起来像是科幻电影里的场景,但今天,借助深度强化学习的力量,我们完全可以在自己的电脑上构建并训练出这样一个智能体。本文将带你从零开始,手把手实现一个基于深度Q网络(DQN)的无人机避障系统。我们将使用Python和PyTorch,从最基础的环境模拟器搭建,到DQN算法的核心实现,再到模型训练与调优,最终见证一个“笨拙”的智能体如何通过反复试错,成长为一位敏捷的“飞行高手”。无论你是对强化学习充满好奇的开发者,还是希望将AI应用于机器人控制领域的爱好者,这篇详尽的实战指南都将为你提供一条清晰的路径。
1. 项目概述与环境设计
在开始敲代码之前,我们必须清晰地定义我们要解决的问题。我们的目标是训练一个无人机智能体,在一个二维或简化的三维栅格环境中,从起点出发,避开随机分布的障碍物,最终安全抵达目标点。为了简化问题、聚焦于算法核心,我们通常会先从一个二维平面环境开始。
为什么选择DQN? 深度Q网络是深度强化学习的里程碑式算法,它成功地将深度神经网络与Q-Learning结合,解决了传统强化学习在高维状态空间(如图像)下的“维度灾难”问题。对于无人机避障,环境状态(如传感器数据、周围障碍物信息)往往是高维且连续的,DQN正是处理这类问题的利器。
我们的项目将分为几个核心模块:
- 环境模拟器 (Environment):定义状态、动作、奖励和状态转移规则。
- 智能体 (Agent):包含DQN神经网络和算法逻辑,负责根据状态选择动作。
- 经验回放缓冲区 (Replay Buffer):存储智能体的交互经验,用于打破数据间的相关性,提高学习稳定性。
- 训练循环 (Training Loop):组织上述模块,进行迭代学习。
首先,我们来构建一个简单但足够有挑战性的环境。我们将使用 gymnasium(原OpenAI Gym的维护分支)作为环境接口标准,这有利于我们未来迁移到更复杂的环境。
import gymnasium as gym
from gymnasium import spaces
import numpy as np
import pygame
import random
class DroneObstacleEnv(gym.Env):
"""
自定义无人机避障环境。
环境是一个二维网格世界,无人机需要从起点(S)移动到目标(G),避开障碍物(O)。
"""
metadata = {'render_modes': ['human', 'rgb_array'], 'render_fps': 4}
def __init__(self, grid_size=10, render_mode=None):
super().__init__()
self.grid_size = grid_size
self.render_mode = render_mode
self.window_size = 512 # 渲染窗口的像素尺寸
# 动作空间: 0:上, 1:下, 2:左, 3:右
self.action_space = spaces.Discrete(4)
# 状态空间: 无人机当前位置坐标 (x, y)
# 为了提供更多信息,我们也可以将目标位置和最近障碍物的相对位置作为状态的一部分。
# 这里我们先使用一个简化的状态:仅包含自身位置。
# 状态将被扁平化为一个长度为2的向量。
self.observation_space = spaces.Box(low=0, high=grid_size-1, shape=(2,), dtype=np.int32)
# 初始化起点、目标、障碍物和无人机位置
self._agent_location = None
self._target_location = None
self._obstacles = []
self._generate_obstacles(num_obstacles=5)
# Pygame渲染相关
self.window = None
self.clock = None
def _generate_obstacles(self, num_obstacles):
"""在网格中随机生成不重叠的障碍物,并避开起点和目标。"""
self._obstacles = []
possible_positions = [(x, y) for x in range(self.grid_size) for y in range(self.grid_size)]
# 暂时移除起点和目标(稍后设置)
for _ in range(num_obstacles):
if not possible_positions:
break
pos = random.choice(possible_positions)
self._obstacles.append(pos)
possible_positions.remove(pos)
def _get_obs(self):
"""返回当前观察值(状态)。"""
return np.array(self._agent_location, dtype=np.int32)
def _get_info(self):
"""返回辅助信息(用于调试,不用于训练)。"""
return {
"distance_to_target": np.linalg.norm(np.array(self._agent_location) - np.array(self._target_location)),
"is_collision": self._is_collision(self._agent_location)
}
def _is_collision(self, pos):
"""检查给定位置是否与障碍物或边界碰撞。"""
x, y = pos
# 检查边界
if x < 0 or x >= self.grid_size or y < 0 or y >= self.grid_size:
return True
# 检查障碍物
if tuple(pos) in self._obstacles:
return True
return False
def reset(self, seed=None, options=None):
super().reset(seed=seed)
# 随机设置起点和目标,确保它们不在障碍物上且不重合
possible_positions = [(x, y) for x in range(self.grid_size) for y in range(self.grid_size)]
for obs in self._obstacles:
possible_positions.remove(obs)
self._agent_location = random.choice(possible_positions)
possible_positions.remove(self._agent_location)
self._target_location = random.choice(possible_positions)
observation = self._get_obs()
info = self._get_info()
if self.render_mode == "human":
self._render_frame()
return observation, info
def step(self, action):
# 动作映射:0:上(y-1), 1:下(y+1), 2:左(x-1), 3:右(x+1)
direction_map = [(0, -1), (0, 1), (-1, 0), (1, 0)]
dx, dy = direction_map[action]
new_x = self._agent_location[0] + dx
new_y = self._agent_location[1] + dy
new_location = (new_x, new_y)
# 默认设置
terminated = False
truncated = False
reward = -0.1 # 每一步的小惩罚,鼓励智能

&spm=1001.2101.3001.5002&articleId=154468298&d=1&t=3&u=f2627dbadc82442cac7d25f5581b2816)
361

被折叠的 条评论
为什么被折叠?



