16 KiB
16 KiB
架构设计文档 / Architecture Document
系统全局视图 / System Overview
┌─────────────────────────────────────────────────────────────────┐
│ train.py (入口) │
│ CLI 解析 → 加载配置 → 初始化环境+算法 → 训练循环 → 保存模型 │
└──────────┬──────────────────────────────────┬───────────────────┘
│ │
▼ ▼
┌─────────────────────┐ ┌──────────────────────────┐
│ envs/ (环境层) │ │ agents/ + baselines/ │
│ │ │ (算法层) │
│ WirelessEnv │◄────────►│ CoMADDPG / 7 baselines │
│ ├─ ChannelModel │ obs, │ ├─ Actor (策略网络) │
│ ├─ SemanticModule │ reward, │ ├─ Critic (价值网络) │
│ └─ step/reset │ done │ ├─ ReplayBuffer │
└─────────────────────┘ │ └─ OUNoise │
└──────────────────────────┘
│
▼
┌──────────────────────────┐
│ utils/ (工具层) │
│ metrics.py (评估指标) │
│ visualization.py (绘图) │
└──────────────────────────┘
│
▼
┌──────────────────────────┐
│ evaluate.py (评估入口) │
│ 8 场景 × 12+ 张图 │
└──────────────────────────┘
模块依赖关系 / Module Dependencies
configs/default.yaml
│
├──► envs/channel_model.py (读取 env.carrier_freq, env.noise_psd, env.subcarrier_spacing)
├──► envs/semantic_module.py (读取 env.rho_max, env.rho_min, env.w1, env.w2)
├──► envs/wireless_env.py (读取 env.* 和 training.max_steps)
│ ├── uses ChannelModel
│ └── uses SemanticModule
│
├──► agents/co_maddpg.py (读取 env.num_subcarriers, training.*, network.*, reward.*)
│ ├── uses Actor (agents/actor.py)
│ ├── uses Critic (agents/critic.py)
│ ├── uses ReplayBuffer (agents/replay_buffer.py)
│ └── uses OUNoise (agents/noise.py)
│
├──► baselines/*.py (各基线复用 Actor, Critic, ReplayBuffer, OUNoise)
│
└──► utils/metrics.py (读取 reward.* 权重)
utils/visualization.py (独立,无配置依赖)
数据流 / Data Flow
训练循环(单个 Episode)
┌─── Episode 开始 ───┐
│ │
▼ │
env.reset() │
→ (obs_s, obs_b) │
│ │
┌───► Step 循环 (200 步) ◄────┐ │
│ │ │ │
│ agent.select_action │ │
│ (obs_s) → act_s │ │
│ (obs_b) → act_b │ │
│ │ │ │
│ env.step(act_s, act_b) │ │
│ → (obs_s', obs_b', │ │
│ rew_s, rew_b, │ │
│ done, info) │ │
│ │ │ │
│ agent.compute_rewards │ │
│ (info) → (r_s, r_b) │ │
│ │ │ │
│ buffer.push( │ │
│ obs_s, obs_b, │ │
│ act_s, act_b, │ │
│ r_s, r_b, │ │
│ obs_s', obs_b', │ │
│ done) │ │
│ │ │ │
│ agent.update() │ │
│ │ │ │
│ not done ────────────────┘ │
│ │
└─── done ─── noise.decay() ──────┘
│
save model + log
环境 step() 内部流程(8 步)
Action Decode Subcarrier Alloc Power Alloc
(act_s, act_b) → Greedy by channel → Equal within group
│ │ │
▼ ▼ ▼
n_sub_s, n_sub_b sem_subs, trad_subs power_matrix (K×N)
│ │
▼ ▼
┌─── SNR = p·|h|²/σ² ───┐
│ │
┌─────────┴──────┐ ┌──────────┴──────┐
│ Traditional │ │ Semantic │
│ Rate = Σ Δf· │ │ avg_SNR → │
│ log₂(1+γ) │ │ SSim(γ̄, ρ) → │
│ QoE_b = min │ │ QoE_s = w1·SSim │
│ (R/R_req, 1) │ │ + w2·(1-ρ/ρ_max)│
└───────┬────────┘ └────────┬─────────┘
│ │
└────────┬───────────────┘
▼
QoE_sys = mean(all QoE)
│
Regenerate channel (block fading)
│
Build (obs_s', obs_b', rew_s, rew_b, done, info)
Co-MADDPG Stackelberg 更新机制 / Stackelberg Update Mechanism
这是本算法的核心创新。更新顺序体现了 Leader-Follower 博弈结构:
┌─────────────────────────────────────────────────────────────────┐
│ Stackelberg Update │
│ │
│ PHASE 1: 更新 Follower (Agent B) / Update Follower First │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 1. Critic B: L_B = (Q_B(s,a) - y_B)² │ │
│ │ y_B = r_B + γ·Q_B'(s', a_s'_target, a_b'_target) │ │
│ │ 2. Actor B: max Q_B(s, a_s_current, π_B(o_b)) │ │
│ │ 3. Soft update: θ_B_target ← τ·θ_B + (1-τ)·θ_B_tgt │ │
│ └───────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ (B's policy is now updated) │
│ │
│ PHASE 2: 更新 Leader (Agent S) / Update Leader with B's BR │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 1. Get B's best response: a_b_br = π_B_updated(o_b) │ │
│ │ .detach() — 不反传梯度给 B │ │
│ │ 2. Critic S: L_S = (Q_S(s, a) - y_S)² │ │
│ │ 3. Actor S: max Q_S(s, π_S(o_s), a_b_br) │ │
│ │ Leader 优化时考虑了 Follower 的最优响应 │ │
│ │ 4. Soft update: θ_S_target ← τ·θ_S + (1-τ)·θ_S_tgt │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ PHASE 3: 更新动态 λ / Update Dynamic λ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ λ(t) = sigmoid(β · (QoE_sys - Q_th)) │ │
│ │ β=5.0 控制切换陡度,Q_th=0.6 为切换阈值 │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
奖励计算流程 / Reward Computation Flow
env.step() 输出 info dict
│
┌─────────┴─────────┐
│ qoe_semantic │
│ qoe_traditional │
│ qoe_sys │
└─────────┬─────────┘
│
agent.compute_rewards(info)
│
┌────────────┴────────────┐
▼ ▼
r_coop_s = r_coop_b =
0.5·qoe_s + 0.5·qoe_b +
0.3·qoe_b + 0.3·qoe_s +
0.2·qoe_sys 0.2·qoe_sys
│ │
r_comp_s = r_comp_b =
0.8·qoe_s + 0.8·qoe_b +
0.2·qoe_sys 0.2·qoe_sys
│ │
└────────────┬────────────┘
│
λ = sigmoid(β·(qoe_sys - Q_th))
│
r_s = λ·r_coop_s + (1-λ)·r_comp_s
r_b = λ·r_coop_b + (1-λ)·r_comp_b
观察空间与动作空间 / Observation & Action Spaces
观察空间 (obs_dim = N + 4 = 68)
| 维度 | 内容 (语义 Agent S) | 内容 (传统 Agent B) |
|---|---|---|
| [0 : N] | 语义用户平均信道功率 (归一化) | 传统用户平均信道功率 (归一化) |
| [N] | qoe_avg_s (滚动平均 QoE) | qoe_avg_b (滚动平均 QoE) |
| [N+1] | content_sensitivity | business_priority |
| [N+2] | alloc_s (当前子载波分配比) | alloc_b (当前子载波分配比) |
| [N+3] | load_s (流量负载) | load_b (流量负载) |
动作空间 (act_dim = 3, 连续 [0,1])
| 维度 | 含义 (语义 Agent S) | 含义 (传统 Agent B) |
|---|---|---|
| [0] | 请求子载波比例 n_sub_frac | 请求子载波比例 n_sub_frac |
| [1] | 功率分配比例 p_frac | 功率分配比例 p_frac |
| [2] | 压缩率 ρ (映射到 [ρ_min, ρ_max]) | 冗余参数 (未使用) |
网络架构 / Network Architecture
Actor Network
Input: obs (68,)
│
├─ Linear(68 → 256) + ReLU
├─ Linear(256 → 256) + ReLU
├─ Linear(256 → 128) + ReLU
├─ Linear(128 → 3)
└─ (Tanh + 1) / 2 → output ∈ [0, 1]³
Critic Network (Joint, CTDE)
Input: concat(obs_s, obs_b, act_s, act_b) = (142,)
│
├─ Linear(142 → 512) + ReLU
├─ Linear(512 → 512) + ReLU
├─ Linear(512 → 256) + ReLU
└─ Linear(256 → 1) → Q-value (scalar)
经验回放 / Replay Buffer
9-field transitions:
Transition = (obs_s, obs_b, act_s, act_b, rew_s, rew_b, next_obs_s, next_obs_b, done)
(68,) (68,) (3,) (3,) (1,) (1,) (68,) (68,) (1,)
- 容量: 100,000 transitions
- 采样: 均匀随机采样 batch_size=256
- 存储: deque 结构,FIFO 淘汰
Agent 接口契约 / Agent Interface Contract
所有 8 个算法必须实现以下接口,以兼容 train.py 的训练循环:
class AgentInterface:
# 必须有以下属性之一
self.buffer: ReplayBuffer # 优先检查
self.replay_buffer: ReplayBuffer # 备选
# 选择动作
def select_action(obs_s, obs_b, explore=True) -> (act_s, act_b)
# 计算奖励
def compute_rewards(info) -> (rew_s, rew_b)
# 更新网络
def update() -> dict or None
# 保存/加载模型
def save(path)
def load(path)
# 噪声衰减 (可选,train.py 通过 hasattr 检查)
self.noise_s: OUNoise # 需有 decay_sigma(episode) 方法
self.noise_b: OUNoise
信道模型 / Channel Model
基于 3GPP Urban Micro (UMi) NLOS 模型:
距离: d ~ U(50, 500) 米
路径损耗: PL(d) = 36.7·log₁₀(d) + 22.7 + 26·log₁₀(fc) [dB]
信道增益: h_{k,n} ~ CN(0, 10^{-PL/10}) (Rayleigh fading)
噪声功率: σ² = 10^{(N₀_dBm - 30)/10} · Δf [W]
SNR: γ_{k,n} = p_{k,n} · |h_{k,n}|² / σ²
- 块衰落模型 (Block fading): 每个 step 重新生成信道
- K_s=3 语义用户 + K_b=3 传统用户 = 6 users
- N=64 个 OFDM 子载波
- 子载波分配: 贪婪算法 (语义用户优先)
- 功率分配: 组内均分
设计决策 / Design Decisions
1. 为什么用 Stackelberg 而不是 Nash?
- Stackelberg 适合异构场景:语义用户 (Leader) 先决策,传统用户 (Follower) 最优响应
- 保证了均衡存在性(定理 1-2 在论文中证明)
2. 为什么 λ(t) 用 sigmoid?
- 连续可微,适合梯度训练
- β 参数控制切换陡度,Q_th 控制切换点
- 系统 QoE 高时偏合作 (λ→1),低时偏竞争 (λ→0)
3. 为什么观察空间包含额外 4 维?
- 仅信道信息不够:agent 需要知道当前 QoE 水平、流量负载、分配状况
- 这些额外信息帮助 agent 做出更有环境感知的决策
4. 为什么 Critic 是联合的 (CTDE)?
- 集中训练时可访问所有信息,解决非平稳性问题
- 分散执行时只用各自的 Actor,降低通信开销
5. 为什么语义用户优先分配子载波?
- 体现 Leader 的先动优势 (First-mover advantage)
- 与 Stackelberg 博弈结构一致