- 新增 RL_Algothrithms 模块,包含 A2C、QAC 智能体 - 添加 SAC 章节笔记和 C10 笔记 - 上传训练结果图片 - 完善 README 与 .gitignore
1.0 MiB
1.0 MiB
In [43]:
import torch
import torch.nn as nn
import numpy as np
import math
# 设置随机种子,保证每次运行的结果可复现
torch.manual_seed(42)
# ==========================================
# 超参数与系统参数设置
# ==========================================
gamma = 0.3 # 折扣因子
u_bar = 0.4 # 控制饱和上限 (物理极限)
R = 1.0 # 控制输入惩罚权重
lr_c = 0.05 # Critic 网络的学习率
lr_a = 0.05 # Actor 网络的学习率
# 状态惩罚矩阵 Q1
# 论文中 Q=20I,由于增广状态 X = [e, r],且只有误差 e 需要惩罚,所以后两项为0
Q1 = torch.diag(torch.tensor([20.0, 20.0, 0.0, 0.0], dtype=torch.float32))In [44]:
# ==========================================
# 系统动态与参考轨迹定义
# ==========================================
def system_step(x, u):
"""
非线性离散时间系统漂移与输入动态
x: 当前状态向量 [x1, x2]
u: 控制输入标量
返回: 下一时刻的状态 [x1_next, x2_next]
"""
x1_next = -0.8 * x[1]
x2_next = -0.45 * x[0] - torch.sin(x[1]) + 0.2 * x[1] * u
return torch.stack([x1_next, x2_next])
def ref_step(r):
"""
命令发生器动态 (产生振荡参考轨迹)
r: 当前参考状态向量 [r1, r2]
返回: 下一时刻的参考状态
"""
r1_next = -r[0]
r2_next = -r[1]
return torch.stack([r1_next, r2_next])
def get_G_matrix(x):
"""
计算输入增益矩阵 G(X)
对应于被控系统中控制量 u 前面的状态系数矩阵
用于 Actor 目标控制量的解析计算
"""
return torch.tensor([[0.0], [0.2 * x[1]], [0.0], [0.0]], dtype=torch.float32)In [45]:
# ==========================================
# 神经网络定义与初始化
# ==========================================
class CriticNet(nn.Module):
def __init__(self):
super(CriticNet, self).__init__()
# Critic网络: 4个输入 -> 5个隐藏层节点(tanh) -> 1个标量输出(线性)
self.fc1 = nn.Linear(4, 5)
self.fc2 = nn.Linear(5, 1)
# 将权重初始化在 -1 到 1 之间
nn.init.uniform_(self.fc1.weight, -1.0, 1.0)
nn.init.uniform_(self.fc2.weight, -1.0, 1.0)
def forward(self, X):
h = torch.tanh(self.fc1(X))
V = self.fc2(h)
return V
class ActorNet(nn.Module):
def __init__(self):
super(ActorNet, self).__init__()
# Actor网络: 4个输入 -> 5个隐藏层节点(tanh) -> 1个控制量输出
self.fc1 = nn.Linear(4, 5)
self.fc2 = nn.Linear(5, 1)
nn.init.uniform_(self.fc1.weight, -1.0, 1.0)
nn.init.uniform_(self.fc2.weight, -1.0, 1.0)
def forward(self, X):
h = torch.tanh(self.fc1(X))
# 核心:输出层包含物理饱和约束 u_bar * tanh(.)
u = u_bar * torch.tanh(self.fc2(h))
return u
# 实例化网络与对应的随机梯度下降优化器
critic = CriticNet()
actor = ActorNet()
optimizer_c = torch.optim.SGD(critic.parameters(), lr=lr_c)
optimizer_a = torch.optim.SGD(actor.parameters(), lr=lr_a)In [46]:
# ==========================================
# 辅助函数:非二次型积分代价计算
# ==========================================
def non_quadratic_cost(u):
"""
计算包含输入约束的非二次型积分代价 W(u)
防止控制量在优化过程中无限制增长
"""
# 稍微截断控制量,防止在反三角函数中出现数值溢出 (除以0或虚数)
u_clamped = torch.clamp(u, -u_bar + 1e-5, u_bar - 1e-5)
ratio = u_clamped / u_bar
# 积分的解析解公式
integral = (ratio * torch.atanh(ratio)) + 0.5 * torch.log(1 - ratio**2)
return 2 * (u_bar**2) * R * integralIn [47]:
# ==========================================
# 核心在线学习主循环
# ==========================================
total_steps = 500
# 初始化历史状态序列 (给一个初始偏差,观察跟踪效果)
x_k_minus_1 = torch.tensor([1.0, -1.0], dtype=torch.float32)
r_k_minus_1 = torch.tensor([0.5, -0.5], dtype=torch.float32)
for k in range(1, total_steps + 1):
# --- 第1部分:前向交互与数据采集 ---
# 计算 k-1 时刻的误差和增广状态 X(k-1)
e_k_minus_1 = x_k_minus_1 - r_k_minus_1
X_k_minus_1 = torch.cat([e_k_minus_1, r_k_minus_1])
# 网络生成策略,并加入探测噪声以满足持续激励(PE)条件
u_k_minus_1 = actor(X_k_minus_1)
noise = torch.tensor([0.05 * math.sin(k)], dtype=torch.float32)
u_applied = torch.clamp(u_k_minus_1 + noise, -u_bar, u_bar)
# 物理系统演化:得到当前真正的 k 时刻状态
x_k = system_step(x_k_minus_1, u_applied[0])
r_k = ref_step(r_k_minus_1)
e_k = x_k - r_k
X_k = torch.cat([e_k, r_k])
# --- 第2部分:Critic 网络时移更新 ---
optimizer_c.zero_grad()
V_k_minus_1 = critic(X_k_minus_1)
# 截断 k 时刻价值的梯度,把它当作目标常数
V_k = critic(X_k).detach()
# 计算历史这一步的真实单步代价
state_cost = torch.matmul(torch.matmul(X_k_minus_1.T, Q1), X_k_minus_1)
ctrl_cost = non_quadratic_cost(u_applied[0])
U_k_minus_1 = state_cost + ctrl_cost
# 必须将 td_target 彻底切断联系,不让它把梯度传导给 Actor
td_target = (U_k_minus_1 + gamma * V_k).detach()
# 计算 TD 误差并反向传播更新 Critic
td_target = U_k_minus_1 + gamma * V_k
loss_c = 0.5 * (td_target - V_k_minus_1)**2
loss_c.backward()
optimizer_c.step()
# --- 第3部分:Actor 网络时移更新 ---
optimizer_a.zero_grad()
# 重新前向传播以构建计算图
u_pred = actor(X_k_minus_1)
# 切断 X_k 与过去系统的联系,仅把它当做一个自变量来求偏导
X_k_for_grad = X_k.clone().detach().requires_grad_(True)
V_k_grad = critic(X_k_for_grad)
# 使用 torch.autograd.grad 纯净地求导,避免对 Critic 权重二次污染
dV_dX = torch.autograd.grad(outputs=V_k_grad, inputs=X_k_for_grad)[0]
# 计算目标控制量 (结合输入增益矩阵 G)
G_matrix = get_G_matrix(x_k_minus_1)
inner_term = - (gamma / 2.0) * (1.0 / (u_bar * R)) * torch.matmul(G_matrix.T, dV_dX)
u_target = u_bar * torch.tanh(inner_term).detach()
# 最小化 Actor 输出与解析目标控制量的误差
loss_a = 0.5 * (u_target - u_pred)**2
loss_a.backward()
optimizer_a.step()
# --- 第4部分:时间轴步进 ---
x_k_minus_1 = x_k.detach()
r_k_minus_1 = r_k.detach()
# 每 10 步打印一次学习进度
if k % 10 == 0:
print(f"当前步数 {k}: Critic 损失 = {loss_c.item():.4f}, Actor 损失 = {loss_a.item():.4f}")当前步数 10: Critic 损失 = 664.4067, Actor 损失 = 0.0018 当前步数 20: Critic 损失 = 51.7882, Actor 损失 = 0.0008 当前步数 30: Critic 损失 = 1.2308, Actor 损失 = 0.0004 当前步数 40: Critic 损失 = 0.0579, Actor 损失 = 0.0001 当前步数 50: Critic 损失 = 0.7083, Actor 损失 = 0.0001 当前步数 60: Critic 损失 = 0.7751, Actor 损失 = 0.0000 当前步数 70: Critic 损失 = 0.4341, Actor 损失 = 0.0000 当前步数 80: Critic 损失 = 0.0691, Actor 损失 = 0.0000 当前步数 90: Critic 损失 = 0.0727, Actor 损失 = 0.0000 当前步数 100: Critic 损失 = 0.4509, Actor 损失 = 0.0000 当前步数 110: Critic 损失 = 0.7882, Actor 损失 = 0.0000 当前步数 120: Critic 损失 = 0.6399, Actor 损失 = 0.0000 当前步数 130: Critic 损失 = 0.2181, Actor 损失 = 0.0000 当前步数 140: Critic 损失 = 0.0008, Actor 损失 = 0.0000 当前步数 150: Critic 损失 = 0.2530, Actor 损失 = 0.0000 当前步数 160: Critic 损失 = 0.6776, Actor 损失 = 0.0000 当前步数 170: Critic 损失 = 0.7690, Actor 损失 = 0.0000 当前步数 180: Critic 损失 = 0.4190, Actor 损失 = 0.0000 当前步数 190: Critic 损失 = 0.0433, Actor 损失 = 0.0000 当前步数 200: Critic 损失 = 0.0860, Actor 损失 = 0.0000 当前步数 210: Critic 损失 = 0.4970, Actor 损失 = 0.0000 当前步数 220: Critic 损失 = 0.7905, Actor 损失 = 0.0000 当前步数 230: Critic 损失 = 0.6150, Actor 损失 = 0.0000 当前步数 240: Critic 损失 = 0.1838, Actor 损失 = 0.0000 当前步数 250: Critic 损失 = 0.0039, Actor 损失 = 0.0000 当前步数 260: Critic 损失 = 0.2894, Actor 损失 = 0.0000 当前步数 270: Critic 损失 = 0.7027, Actor 损失 = 0.0000 当前步数 280: Critic 损失 = 0.7510, Actor 损失 = 0.0000 当前步数 290: Critic 损失 = 0.3837, Actor 损失 = 0.0000 当前步数 300: Critic 损失 = 0.0299, Actor 损失 = 0.0000 当前步数 310: Critic 损失 = 0.1111, Actor 损失 = 0.0000 当前步数 320: Critic 损失 = 0.5306, Actor 损失 = 0.0000 当前步数 330: Critic 损失 = 0.7897, Actor 损失 = 0.0000 当前步数 340: Critic 损失 = 0.5876, Actor 损失 = 0.0000 当前步数 350: Critic 损失 = 0.1564, Actor 损失 = 0.0000 当前步数 360: Critic 损失 = 0.0110, Actor 损失 = 0.0000 当前步数 370: Critic 损失 = 0.3220, Actor 损失 = 0.0000 当前步数 380: Critic 损失 = 0.7205, Actor 损失 = 0.0000 当前步数 390: Critic 损失 = 0.7392, Actor 损失 = 0.0000 当前步数 400: Critic 损失 = 0.3486, Actor 损失 = 0.0000 当前步数 410: Critic 损失 = 0.0171, Actor 损失 = 0.0000 当前步数 420: Critic 损失 = 0.1345, Actor 损失 = 0.0000 当前步数 430: Critic 损失 = 0.5621, Actor 损失 = 0.0000 当前步数 440: Critic 损失 = 0.7964, Actor 损失 = 0.0000 当前步数 450: Critic 损失 = 0.5534, Actor 损失 = 0.0000 当前步数 460: Critic 损失 = 0.1282, Actor 损失 = 0.0000 当前步数 470: Critic 损失 = 0.0198, Actor 损失 = 0.0000 当前步数 480: Critic 损失 = 0.3579, Actor 损失 = 0.0000 当前步数 490: Critic 损失 = 0.7433, Actor 损失 = 0.0000 当前步数 500: Critic 损失 = 0.7149, Actor 损失 = 0.0000
In [48]:
import matplotlib.pyplot as plt
# ==========================================
# 算法验证与结果可视化
# ==========================================
# 切换为评估模式 (在此阶段不需要计算梯度)
actor.eval()
# 验证阶段的步数
eval_steps = 500
# 重新初始化状态,用于纯净的闭环测试
x_eval = torch.tensor([1.5, -1.5], dtype=torch.float32)
r_eval = torch.tensor([1.4, -1.4], dtype=torch.float32)
# 用于存放画图数据的列表
x1_history, x2_history = [], []
r1_history, r2_history = [], []
e1_history, e2_history = [], []
# 纯净闭环控制循环 (无梯度更新,无探测噪声)
with torch.no_grad():
for k in range(eval_steps):
# 记录当前时刻的数据
x1_history.append(x_eval[0].item())
x2_history.append(x_eval[1].item())
r1_history.append(r_eval[0].item())
r2_history.append(r_eval[1].item())
# 记录跟踪误差 e = x - r
e_eval = x_eval - r_eval
e1_history.append(e_eval[0].item())
e2_history.append(e_eval[1].item())
# 拼接增广状态
X_eval = torch.cat([e_eval, r_eval])
# 使用训练好的 Actor 网络计算控制律 (注意:这里去掉了 noise)
u_eval = actor(X_eval)
# 加上物理限幅保护
u_applied = torch.clamp(u_eval, -u_bar, u_bar)
# 系统状态演化
x_eval = system_step(x_eval, u_applied[0])
r_eval = ref_step(r_eval)
# ==========================================
# 使用 Matplotlib 绘制跟踪效果图
# ==========================================
time_steps = range(eval_steps)
# 设置全局字体大小,方便放入大论文
plt.rcParams.update({'font.size': 12})
# 绘制状态 x1 与参考轨迹 r1 的对比图 (对应论文 Fig. 6)
plt.figure(figsize=(10, 5))
plt.plot(time_steps, x1_history, label='x1 (System State)', color='red', linewidth=1.5)
plt.plot(time_steps, r1_history, label='r1 (Reference)', color='green', linestyle='--', linewidth=1.5)
plt.title('Tracking Performance: x1 and r1')
plt.xlabel('Time step')
plt.ylabel('x1 and r1')
plt.legend(loc='upper right')
plt.grid(True)
plt.xlim([0, eval_steps])
plt.show()
# 绘制状态 x2 与参考轨迹 r2 的对比图 (对应论文 Fig. 7)
plt.figure(figsize=(10, 5))
plt.plot(time_steps, x2_history, label='x2 (System State)', color='red', linewidth=1.5)
plt.plot(time_steps, r2_history, label='r2 (Reference)', color='green', linestyle='--', linewidth=1.5)
plt.title('Tracking Performance: x2 and r2')
plt.xlabel('Time step')
plt.ylabel('x2 and r2')
plt.legend(loc='upper right')
plt.grid(True)
plt.xlim([0, eval_steps])
plt.show()
# 新增:绘制误差曲线 e1 和 e2
plt.figure(figsize=(10, 5))
plt.plot(time_steps, e1_history, label='e1 = x1 - r1', color='blue', linewidth=1.5)
plt.plot(time_steps, e2_history, label='e2 = x2 - r2', color='orange', linewidth=1.5)
plt.axhline(0.0, color='black', linestyle='--', linewidth=1.0)
plt.title('Tracking Errors: e1 and e2')
plt.xlabel('Time step')
plt.ylabel('Tracking error')
plt.legend(loc='upper right')
plt.grid(True)
plt.xlim([0, eval_steps])
plt.show()