
簡介本資源面向計算機、自動化等專業(yè)的學生與開發(fā)者提供基于Python與DQN強化學習實現海防場景導彈目標選擇任務的完整項目方案適用于畢業(yè)設計、課程設計及項目開發(fā)參考。任務中敵方艦艇以固定陣型排列我方18枚導彈需依次選擇攻擊目標并沿直線軌跡飛行進入防御艦艇防御半徑后存在被攔截概率艦艇被命中一定次數后死亡且可通過調整不同艦艇價值改變攻擊側重點核心在于合理規(guī)劃攻擊目標與順序以實現期望傷害最大化。資源包共474個文件約80.7MB包含15個Python源碼文件、108組模型checkpoint與data分片、6個yml配置、2張png示意圖、1個mp4演示視頻及md項目文檔覆蓋訓練權重、配置與說明材料。已有40人學習下載讀者可獲取可運行源碼、算法解析文檔與演示視頻并在此基礎上延伸改造。1. 導彈目標選擇任務為什么適合用 DQN 入門強化學習導彈目標選擇本質是在多目標、多威脅、多約束的戰(zhàn)場態(tài)勢里讓導彈在飛行中動態(tài)決定“打誰、先打誰、要不要換目標”。傳統做法是寫死優(yōu)先級規(guī)則比如“先打距離最近”或“先打威脅值最高”但真實場景里目標會機動、威脅會變化、攔截概率會波動規(guī)則一多就互相打架。DQNDeep Q-Network把這件事建模成序貫決策狀態(tài)是當前態(tài)勢動作是選擇某個目標獎勵綜合命中概率、威脅削減和資源消耗讓網絡自己學出策略。Python 生態(tài)里 PyTorch 或 TensorFlow 都能快速搭出可訓練的 DQN配合仿真環(huán)境就能跑通從環(huán)境封裝、經驗回放到目標網絡更新的完整鏈路。這套東西適合畢業(yè)設計、課程設計里需要“有算法、有代碼、有可視化”的題目也適合想從零理解強化學習落地流程的開發(fā)者——它比打游戲更貼近工程約束又比真實火控系統簡單到能在一臺筆記本上復現。2. 把導彈目標選擇建模成 MDP狀態(tài)、動作、獎勵怎么定2.1 狀態(tài)空間設計別把原始坐標直接塞給網絡狀態(tài)設計是 DQN 能不能收斂的第一道坎。常見做法是把每個目標的信息整理成固定長度的向量再拼上導彈自身狀態(tài)。假設場景里有 N 個目標每個目標提取 6 個特征相對距離、相對方位角、相對速度、威脅等級、目標類型編碼、是否已被分配。導彈自身狀態(tài)包括剩余燃料、當前速度、當前高度。這樣狀態(tài)維度就是6 * N 3。如果 N 會變化要么固定最大目標數并用掩碼標記無效目標要么用注意力機制但入門階段建議固定 N比如 N5先把流程跑通。import numpy as np def build_state(missile, targets, max_targets5): missile: dict, 包含 fuel, speed, altitude targets: list of dict, 每個目標含 dist, angle, rel_speed, threat, type_id, assigned 返回: np.array, shape (6 * max_targets 3,) state [] for i in range(max_targets): if i len(targets): t targets[i] state.extend([ t[dist] / 10000.0, # 歸一化到 0~1 附近 t[angle] / np.pi, # 角度歸一化 t[rel_speed] / 1000.0, t[threat] / 10.0, t[type_id] / 5.0, 1.0 if t[assigned] else 0.0 ]) else: state.extend([0.0] * 6) # 無效目標補零 state.extend([ missile[fuel] / 100.0, missile[speed] / 2000.0, missile[altitude] / 15000.0 ]) return np.array(state, dtypenp.float32)這段代碼的關鍵是歸一化。距離除以 10000、速度除以 1000 這類操作不是可選項而是必須做——DQN 對輸入尺度敏感未歸一化的特征會讓梯度更新方向被大數值主導表現為 loss 震蕩不降。assigned字段用來標記該目標是否已經被其他導彈鎖定避免多彈重復打同一個目標這是多導彈協同里常見的約束。如果目標數少于 max_targets補零并配合動作掩碼防止網絡選到無效目標。2.2 動作空間與獎勵函數稀疏獎勵是最大的坑動作空間最直接的設計是離散化動作 選擇第 i 個目標i 從 0 到 N-1再加上一個“保持當前目標”的動作總共 N1 個離散動作。這樣 DQN 的輸出層就是 N1 個 Q 值取 argmax 即可。不要一上來就做連續(xù)動作空間那需要 DDPG 或 SAC復雜度翻倍畢業(yè)設計周期內很難調穩(wěn)。獎勵函數決定策略長什么樣。一個可用的獎勵結構是命中目標100同時按目標威脅值加成比如100 threat * 10導彈被攔截或燃料耗盡-50每步時間懲罰-0.1鼓勵盡快決策切換目標懲罰-1避免頻繁換目標導致震蕩選中已分配目標-5懲罰重復分配def compute_reward(missile, target, done, hit, fuel_out, switched, already_assigned): reward -0.1 # 每步基礎懲罰 if hit: reward 100 target[threat] * 10 if fuel_out: reward - 50 if switched: reward - 1 if already_assigned: reward - 5 return reward血淚經驗是稀疏獎勵下 DQN 幾乎學不動。如果只有命中才給獎勵前期隨機探索很難碰到命中Q 值長期為 0網絡輸出退化為常數。解決辦法是加中間獎勵比如“距離縮短”給小額正獎勵“威脅值降低”也給正獎勵讓 agent 有梯度可爬。但中間獎勵不能太大否則 agent 會刷獎勵而不去命中這就是 reward hacking。我一般把中間獎勵控制在每步 ±0.5 以內命中獎勵保持 100 量級比例大約 200:1。3. 用 PyTorch 搭 DQN網絡結構、經驗回放和目標網絡3.1 Q 網絡結構兩層全連接夠用別堆太深導彈目標選擇的狀態(tài)維度通常在 30 到 50 之間動作數在 6 到 10 之間這種規(guī)模用兩層 128 維全連接加 ReLU 就足夠。層數太深反而容易過擬合訓練慢且難調。輸出層不加激活函數直接輸出 Q 值。import torch import torch.nn as nn class QNetwork(nn.Module): def __init__(self, state_dim, action_dim, hidden128): super().__init__() self.net nn.Sequential( nn.Linear(state_dim, hidden), nn.ReLU(), nn.Linear(hidden, hidden), nn.ReLU(), nn.Linear(hidden, action_dim) ) def forward(self, x): return self.net(x)state_dim由上一章的6 * max_targets 3決定action_dim是max_targets 1。hidden 取 128 是經驗值64 可能欠擬合256 在幾千條經驗的數據量下容易過擬合。如果訓練 loss 下降但評估回報不漲優(yōu)先檢查是不是網絡太大記住了訓練樣本。3.2 經驗回放與目標網絡兩個穩(wěn)定訓練的關鍵組件DQN 相比普通 Q-learning 的核心改進就是經驗回放和目標網絡。經驗回放把(state, action, reward, next_state, done)存進緩沖區(qū)訓練時隨機采樣打破時間相關性。目標網絡是 Q 網絡的副本每隔 C 步同步一次參數用來計算 TD 目標避免“自己追自己”導致發(fā)散。import random from collections import deque class ReplayBuffer: def __init__(self, capacity10000): self.buffer deque(maxlencapacity) def push(self, state, action, reward, next_state, done): self.buffer.append((state, action, reward, next_state, done)) def sample(self, batch_size): batch random.sample(self.buffer, batch_size) states, actions, rewards, next_states, dones zip(*batch) return (np.stack(states), np.array(actions), np.array(rewards, dtypenp.float32), np.stack(next_states), np.array(dones, dtypenp.float32)) def __len__(self): return len(self.buffer)容量設 10000 到 50000 都常見。太小則樣本重復率高容易過擬合近期經驗太大則早期差經驗長期污染緩沖區(qū)。我一般從 10000 起步訓練穩(wěn)定后如果發(fā)現策略退化再考慮減小容量或加優(yōu)先級采樣。目標網絡同步步數 C 通常取 100 到 500環(huán)境步數多就取大一點。同步太頻繁等于沒有目標網絡同步太慢則目標滯后嚴重TD 誤差長期偏大。3.3 訓練循環(huán)epsilon 衰減和梯度裁剪訓練循環(huán)里兩個參數最影響成敗epsilon 衰減和梯度裁剪。epsilon 控制探索率從 1.0 線性或指數衰減到 0.05 左右。衰減太快則探索不足策略早熟衰減太慢則訓練前期大量隨機動作收斂慢。梯度裁剪用torch.nn.utils.clip_grad_norm_限制梯度范數防止個別 batch 的 TD 誤差過大把網絡打飛。def train_step(q_net, target_net, optimizer, buffer, batch_size, gamma): if len(buffer) batch_size: return None states, actions, rewards, next_states, dones buffer.sample(batch_size) states torch.FloatTensor(states) actions torch.LongTensor(actions) rewards torch.FloatTensor(rewards) next_states torch.FloatTensor(next_states) dones torch.FloatTensor(dones) q_values q_net(states).gather(1, actions.unsqueeze(1)).squeeze(1) with torch.no_grad(): next_q target_net(next_states).max(1)[0] target rewards gamma * next_q * (1 - dones) loss nn.MSELoss()(q_values, target) optimizer.zero_grad() loss.backward() torch.nn.utils.clip_grad_norm_(q_net.parameters(), max_norm10.0) optimizer.step() return loss.item()gamma取 0.95 到 0.99。導彈目標選擇是回合制任務一個回合可能幾十步gamma 取 0.95 意味著約 20 步外的獎勵衰減到一半比較合理。max_norm10.0是常用值如果 loss 出現 NaN先降到 1.0 試試。訓練時每 C 步執(zhí)行target_net.load_state_dict(q_net.state_dict())C 取 200 左右。4. 仿真環(huán)境搭建與訓練調參從零跑通第一個回合4.1 用 Gym 風格封裝環(huán)境reset 和 step 的邊界為了復用 DQN 訓練代碼環(huán)境最好按 Gym 接口封裝reset()返回初始狀態(tài)step(action)返回(next_state, reward, done, info)。導彈目標選擇的仿真不需要真實物理引擎用簡化的運動學模型即可目標按預設軌跡移動導彈按當前目標方向飛行每步更新距離和角度。class MissileEnv: def __init__(self, max_targets5, max_steps100): self.max_targets max_targets self.max_steps max_steps self.reset() def reset(self): self.step_count 0 self.missile {fuel: 100.0, speed: 800.0, altitude: 8000.0} self.targets [self._random_target() for _ in range(self.max_targets)] self.current_target None return build_state(self.missile, self.targets, self.max_targets) def step(self, action): self.step_count 1 switched False if action self.max_targets: if self.current_target ! action: switched True self.current_target action # 簡化運動學更新 self.missile[fuel] - 0.5 for t in self.targets: t[dist] - self.missile[speed] * 0.1 hit any(t[dist] 0 for t in self.targets) fuel_out self.missile[fuel] 0 done hit or fuel_out or self.step_count self.max_steps reward compute_reward(self.missile, self.targets[0], done, hit, fuel_out, switched, False) next_state build_state(self.missile, self.targets, self.max_targets) return next_state, reward, done, {}max_steps100是回合上限防止 agent 無限繞圈。fuel每步減 0.5100 步耗盡和 max_steps 對齊。目標距離每步減speed * 0.1這是把速度單位換算成“每步飛行距離”的簡化。真實項目里可以用更細的積分步長但入門階段這個精度足夠驗證算法。注意done的判斷順序先判命中再判燃料最后判步數避免命中時被步數截斷。4.2 超參數表與訓練曲線判讀下面這張表是我調過幾輪后比較穩(wěn)的起點不是唯一解但能讓你第一輪就跑出下降的 loss 和上升的回報。參數取值說明學習率1e-3Adam 優(yōu)化器太大易震蕩太小收斂慢batch_size64經驗回放采樣批量gamma0.95折扣因子回合約 100 步epsilon 起始1.0完全隨機探索epsilon 終止0.05保留少量探索epsilon 衰減步數5000線性衰減目標網絡同步每 200 步硬更新回放緩沖區(qū)10000經驗容量梯度裁剪10.0范數上限訓練曲線判讀有幾個典型模式。Loss 持續(xù)下降但評估回報不漲多半是過擬合或獎勵設計有問題。Loss 震蕩劇烈檢查學習率是否太大或 batch_size 是否太小。回報曲線先升后降常見于 epsilon 衰減太快導致策略早熟或者目標網絡同步太頻繁。我一般每 100 回合跑一次評估評估時 epsilon 設為 0看純貪心策略的表現避免探索噪聲干擾判斷。5. 避坑與排查導彈目標選擇 DQN 訓練中最容易翻車的 5 個點5.1 現象Q 值爆炸成 NaNloss 直接變 inf原因通常是獎勵量級過大或梯度未裁剪。命中獎勵 100 加上威脅加成可能到 200TD 目標里reward gamma * max Q在 Q 值本身已經偏大時進一步放大幾輪更新后超出浮點范圍。解決方法是先把獎勵整體除以 100讓命中獎勵回到 1 量級同時確認clip_grad_norm_已啟用且 max_norm 不超過 10。如果仍然 NaN把學習率降到 1e-4 再試。5.2 現象agent 學會原地不動或反復選同一個目標這是 reward hacking 的典型表現。如果“保持當前目標”有隱性正獎勵或者切換懲罰過大agent 會發(fā)現不動最安全。檢查獎勵函數里是否有未預期的正項比如距離縮短獎勵在目標不動時也持續(xù)給。解決辦法是給“保持當前目標”加微小時間懲罰或者把切換懲罰從 -1 降到 -0.2讓 agent 有動力重新評估。5.3 現象訓練回報波動極大同一策略兩次評估差一倍多半是環(huán)境隨機性太強或評估回合數太少。目標初始位置、速度如果每次 reset 都大幅隨機策略很難穩(wěn)定。把隨機范圍收窄比如距離在 5000 到 10000 之間而不是 1000 到 20000。評估時至少跑 20 個回合取平均單回合結果沒有參考價值。另外檢查回放緩沖區(qū)是否太小導致采樣方差大。5.4 現象loss 正常下降但 agent 從不命中目標這是稀疏獎勵下探索不足的經典問題。epsilon 衰減太快agent 還沒碰到過命中就進入利用階段Q 網絡對命中動作的估值始終是隨機初始值。解決辦法是延長 epsilon 衰減步數到 10000 以上或者加中間獎勵引導。另一個檢查點是動作掩碼如果無效目標的 Q 值沒有被屏蔽agent 可能一直選無效動作表現為“不命中也不報錯”。5.5 現象換一臺機器或換隨機種子后結果完全不可復現DQN 對隨機種子敏感是常態(tài)。要復現結果需要固定 Python 的random.seed、NumPy 的np.random.seed、PyTorch 的torch.manual_seed還要設置torch.backends.cudnn.deterministic True。但即使這樣不同 GPU 上的浮點運算順序差異仍可能導致微小偏差進而影響策略。我的習慣是報告結果時跑 3 個不同種子取均值和標準差而不是只報最好的一次。6. 進階技巧用動作掩碼和優(yōu)先經驗回放把命中率再提一檔動作掩碼解決的是“無效動作被高估”的問題。在導彈目標選擇里已經被分配的目標、距離超出射程的目標、燃料不足以到達的目標都應該在動作選擇時屏蔽。實現方式是在 Q 網絡輸出后把無效動作的 Q 值設為一個極小值再取 argmax。訓練時計算 TD 目標也要用掩碼后的 next Q否則目標網絡會把無效動作的估值傳播回來。def masked_q_values(q_values, valid_mask): q_values: tensor, shape (batch, action_dim) valid_mask: tensor, 1 表示有效, 0 表示無效 invalid_mask (valid_mask 0) q_values q_values.masked_fill(invalid_mask, -1e9) return q_values # 訓練時計算 next_q with torch.no_grad(): next_q_all target_net(next_states) next_q_all masked_q_values(next_q_all, next_valid_masks) next_q next_q_all.max(1)[0]-1e9而不是-inf避免后續(xù)計算出現 NaN。valid_mask 的構造邏輯是目標存在且未被分配且距離在射程內則為 1否則為 0。這個掩碼在環(huán)境 step 里生成隨狀態(tài)一起返回。優(yōu)先經驗回放Prioritized Experience Replay讓 TD 誤差大的樣本被采樣概率更高。實現上可以用 SumTree 或者簡化版給每個樣本存一個優(yōu)先級p |TD error| epsilon采樣時按 p 歸一化后的概率抽。簡化版用 NumPy 的np.random.choice配合p數組即可不用手寫 SumTree。優(yōu)先級參數 alpha 取 0.6重要性采樣權重 beta 從 0.4 線性升到 1.0。這套組合在導彈目標選擇任務里通常能把命中率從 60% 左右提到 80% 以上但訓練時間會增加約 30%。驗證改進是否有效不能只看訓練回報。我一般固定 50 個測試場景每個場景跑 10 次取平均命中率和平均決策步數對比加掩碼前后、加優(yōu)先回放前后的數值。如果命中率提升但決策步數大幅增加說明 agent 在猶豫需要檢查時間懲罰是否太小。最后說個習慣每次改獎勵函數或網絡結構只改一個變量跑完對比再改下一個。同時改三四個地方結果好了也不知道是哪個起作用結果差了更不知道回退哪個。希望幫到你。本文還有配套的精品資源點擊獲取