☰
Actor-Critic强化学习算法:Matlab工程实现与核心代码解析
2026/10/6 5:23:01 网站建设 项目流程

简介:本资源是面向强化学习初学者与Matlab实践者的Actor-Critic算法完整实现包,聚焦于连续/离散控制任务中的策略优化与价值评估协同训练问题,适用于高校学生、科研入门者及工程技术人员开展算法复现与原理验证。压缩包共10个文件,含7个核心MATLAB源码(.m)与3个备份脚本(.asv),涵盖环境仿真(simulator.m)、策略网络更新(AC.m / computpi.m)、价值函数拟合(evaluate.m / computphi.m / computpsi.m)等关键模块,结构清晰、注释充分,便于分步调试与教学演示。资源体积仅5KB,轻量易部署,已吸引460人下载学习。读者可直接运行示例流程,掌握Actor网络参数梯度上升更新、Critic网络均方误差优化、状态-动作联合采样机制等核心实现细节,并基于代码框架快速迁移至CartPole等经典控制任务。

1. 项目概述:Actor-Critic网络的Matlab实现

如果你正在研究强化学习,特别是想找一个能跑通、能修改、能直观看到训练过程的代码框架,那么“actor-critic网络 Matlab.zip”这个项目很可能就是你需要的。它不是一个简单的算法演示,而是一个在Matlab环境下,将Actor-Critic(演员-评论家)这一经典强化学习框架工程化的完整实现包。对于学生、研究人员以及需要在Matlab生态(比如与控制仿真Simulink结合)中快速验证强化学习算法的工程师来说,这个项目提供了一个绝佳的起点。

Actor-Critic算法是强化学习中的一个重要分支,它巧妙地将策略学习(Actor)和价值评估(Critic)结合起来,兼具了策略梯度方法直接优化策略的优势,以及价值函数方法评估状态好坏的能力,从而实现了更稳定、更高效的在线学习。然而,理论上的优雅往往伴随着工程实现的复杂性:神经网络结构如何设计?TD误差如何计算和反向传播?探索与利用的平衡如何把握?这些细节在论文中可能一笔带过,但在代码里却至关重要。

这个Matlab项目包的价值,就在于它把这些理论细节转化为了可运行的代码。你拿到手的不是一个黑箱函数,而是一套结构清晰、模块分明的脚本和函数集合。通过它,你不仅能快速复现一个基础的Actor-Critic智能体,更能深入其内部,观察每一步梯度更新、每一次动作选择背后的逻辑,这对于理解算法的本质至关重要。接下来,我将带你深入拆解这个项目,从整体架构到每一行关键代码,并分享我在复现和调参过程中积累的实战经验。

2. 项目整体架构与设计思路拆解

2.1 Actor-Critic核心思想与项目定位

在深入代码之前,我们必须先厘清Actor-Critic的核心思想,这决定了整个项目的设计骨架。想象一下训练一个机器人走路:Actor(演员)就是机器人的“小脑”,它负责根据当前看到的场景(状态)直接做出动作决策,比如“左腿向前迈15度”。Critic(评论家)则是机器人的“教练”或“评委”,它不直接指挥动作,而是评价Actor刚才在某个状态下做出的动作到底有多好,给出一个分数(价值估计)。

这个“评分”不是凭空而来的,Critic通过不断地观察环境反馈的奖励(比如机器人没有摔倒就给予正奖励)来学习一套自己的评价标准。然后,Critic将它的评价(具体形式常为TD误差)反馈给Actor,告诉它:“你刚才那个动作,比我想象中要好/差,下次在类似情况下,应该更倾向于/避免做这个动作。” Actor就根据这个反馈来调整自己的决策策略(更新神经网络参数)。

这个Matlab项目实现的就是上述过程。它的定位非常明确:一个轻量级、可读性强、便于教学和快速原型开发的Actor-Critic实现。它没有追求极致的性能或支持最复杂的算法变体,而是聚焦于将最基础的A2C(Advantage Actor-Critic)或简单的Actor-Critic模型清晰地呈现出来。项目通常包含以下几个核心模块:环境交互模块、Actor网络、Critic网络、经验回放缓冲区(可能有)、以及主训练循环。代码风格上,它很可能避免使用过于复杂的面向对象设计,而是采用函数式或简单的脚本结构,让初学者也能一目了然。

2.2 项目文件结构解析

解压“actor-critic网络 Matlab.zip”后,你通常会看到类似如下的文件结构。理解每个文件的作用,是上手修改和调试的第一步。

actor-critic-matlab/ ├── main.m # 主训练脚本,程序入口 ├── env/ # 环境模块(可能内置或调用外部环境) │ ├── createEnv.m # 创建并初始化环境 │ └── stepEnv.m # 执行动作,返回新状态和奖励 ├── agent/ # 智能体核心模块 │ ├── actor.m 或 actorNet.m # Actor策略网络定义与前向传播 │ ├── critic.m 或 criticNet.m # Critic价值网络定义与前向传播 │ ├── chooseAction.m # 根据策略网络输出选择动作(含探索) │ └── update.m # 核心更新函数,计算梯度并更新网络参数 ├── utils/ # 工具函数 │ ├── discountRewards.m # 计算折扣回报(如果Critic用MC) │ ├── computeTDError.m # 计算时序差分(TD)误差 │ └── softmax.m # Softmax函数(用于离散动作空间) ├── replayBuffer.m # 经验回放缓冲区类(如果实现) ├── train.m # 封装好的训练循环(可能被main调用) └── plotResults.m # 绘制训练曲线,如每回合奖励

关键文件解读:

  • main.m:这是整个项目的控制中心。它负责设置超参数(学习率、折扣因子、训练回合数等)、初始化环境和智能体、启动训练循环,并在训练结束后保存模型和绘制结果。通常,你需要修改的就是这个文件里的超参数。
  • agent/update.m:这是算法的“心脏”。它实现了最关键的策略梯度更新和值函数更新。你会在这里看到如何用Critic输出的TD误差作为Actor策略梯度的权重(基线),从而减少方差。代码会清晰地展示损失函数的计算和dlgradient或自定义反向传播的过程。
  • env/文件夹:为了通用性,项目可能实现了一个简单的标准环境,如经典的“CartPole”(倒立摆)或“MountainCar”。createEnv定义了状态和动作空间,stepEnv定义了环境动力学和奖励函数。如果你想应用到自己的问题上,修改或替换这个模块是最直接的途径。

注意:不同的实现版本可能在文件组织上略有差异,有些可能将所有网络定义放在一个networks.m文件里,或者将训练循环直接写在main中。但核心功能模块的划分思路是相通的。

2.3 关键技术选型与考量

这个Matlab项目的实现背后,有一系列关键的技术选择,每一个都影响着算法的性能和易用性。

1. 神经网络构建工具的选择:Matlab目前主要有两种方式构建神经网络:Deep Learning Toolbox和自定义前向/反向传播。

  • Deep Learning Toolbox (推荐且常见):项目很可能使用layerGraph,fullyConnectedLayer,reluLayer等来构建网络,并使用dlarray和dlgradient进行自动微分。这种方式代码简洁,与现代深度学习框架(如PyTorch)思路接近,易于理解和扩展。例如,Actor网络的输出层可能是softmaxLayer(离散动作)或tanhLayer(连续动作,后接缩放)。
  • 自定义网络:如果项目较老或为了教学透明,可能会手动实现网络的前向传播和梯度计算。这种方式虽然繁琐,但能让你对梯度流动有“显微镜”级别的观察。在这个项目中,如果看到大量的矩阵乘法和激活函数手动计算,就属于此类。

2. 优化器的选择:策略梯度和值函数更新都需要优化器。Matlab的trainingOptions和trainNetwork主要用于监督学习,在强化学习的在线更新中并不方便。因此,项目极大概率使用了随机梯度下降(SGD)或其变体(如Adam)的自定义实现。你会在更新函数中看到类似这样的代码:

% 使用Adam优化器更新Actor参数 [actorNet, actorState] = adamupdate(actorNet, actorGradients, actorState, actorLearnRate);

这里直接调用了adamupdate函数来更新网络参数和优化器状态。选择Adam是因为它能自适应调整学习率,在RL训练中通常比SGD更稳定、收敛更快。

3. 探索策略的实现:对于离散动作,通常对Actor网络输出的概率分布进行采样。对于连续动作,则常在网络输出的均值上添加噪声(如高斯噪声),噪声方差可以随时间衰减。项目代码中会有一个专门的函数(如chooseAction)来处理这部分逻辑,这是确保智能体有效探索环境的关键。

4. 是否使用经验回放(Replay Buffer):基础的Actor-Critic是on-policy算法,意味着用于更新的数据必须是由当前策略最新产生的。这与DQN等off-policy算法不同。因此,这个基础版本的Matlab实现可能没有经验回放缓冲区,而是使用“即时数据,即时更新”的方式。每个时间步,用当前(s, a, r, s')元组计算TD误差并立即更新。这样实现简单,但数据效率可能较低。更高级的版本可能会引入重要性采样等技术来实现带回放的off-policy Actor-Critic,但这会增加复杂度。

3. 核心代码模块深度解析

3.1 Actor网络与Critic网络的结构定义

让我们深入到最核心的部分:两个神经网络是如何构建的。在Matlab中,这通常通过函数或脚本来定义。

Actor网络(策略网络): 它的输入是状态s(一个向量),输出是动作的概率分布(离散)或动作的参数(如高斯分布的均值和方差,连续)。一个典型的离散动作Actor网络定义可能如下:

function lgraph = createActorNetwork(stateDim, actionDim) % stateDim: 状态维度,例如 CartPole 是4 % actionDim: 动作维度,例如 向左/向右 就是2 layers = [ featureInputLayer(stateDim, 'Name', 'state') % 输入层 fullyConnectedLayer(128, 'Name', 'fc1') % 隐藏层1 reluLayer('Name', 'relu1') fullyConnectedLayer(64, 'Name', 'fc2') % 隐藏层2 reluLayer('Name', 'relu2') fullyConnectedLayer(actionDim, 'Name', 'fc_out') % 输出层,原始分数 softmaxLayer('Name', 'actionProb') % 转换为概率 ]; lgraph = layerGraph(layers); end

这个网络将状态映射为两个动作的概率。在训练时,我们需要从这个概率分布中采样以获得动作,同时也要能计算该动作的对数概率(用于后续的梯度计算)。

Critic网络(价值网络): 它的输入也是状态s,输出是一个标量V(s),代表当前状态的长期价值期望。其结构通常比Actor网络简单一些:

function lgraph = createCriticNetwork(stateDim) layers = [ featureInputLayer(stateDim, 'Name', 'state') fullyConnectedLayer(128, 'Name', 'fc1') reluLayer('Name', 'relu1') fullyConnectedLayer(64, 'Name', 'fc2') reluLayer('Name', 'relu2') fullyConnectedLayer(1, 'Name', 'value') % 输出单个价值估计 % 通常没有激活函数,因为价值可以是任意实数 ]; lgraph = layerGraph(layers); end

实操心得:网络规模与初始化:对于简单环境(如CartPole),上述的128->64隐藏层可能已经足够,甚至更小(如64->32)也能工作。对于更复杂的环境,可能需要加深或加宽网络。权重初始化非常重要,不恰当的初始化可能导致梯度消失或爆炸。Deep Learning Toolbox的默认初始化(Glorot)通常效果不错。如果你手动初始化,可以尝试he或xavier初始化方法。一个常见的坑是输出层的初始化,如果初始输出值太大,Softmax可能会产生极端概率(接近0或1),阻碍探索。

3.2 动作选择与探索机制实现

定义了网络,下一步是如何用它们来交互环境。chooseAction.m函数是这个过程的核心。

对于离散动作(如上述网络),动作选择包含两步:

  1. 前向传播:将当前状态输入Actor网络,得到每个动作的概率probs。
  2. 依概率采样:使用randsample或mnrnd函数根据probs进行随机采样。
function action = chooseActionDiscrete(actorNet, state) % 将状态转换为 dlarray state = dlarray(state, 'CB'); % 'CB' 表示列格式的批数据 % 前向传播,得到动作概率 prob = predict(actorNet, state); prob = extractdata(prob); % 从 dlarray 中提取数据 % 根据概率分布随机采样一个动作 action = randsample(1:length(prob), 1, true, prob); end

这里的关键是采样,而不是直接选择概率最大的动作。这确保了探索性。随着训练进行,网络输出的概率会逐渐集中于最优动作,探索自然减少。

对于连续动作,Actor网络通常输出动作的均值(mu),并附带一个可学习或固定的标准差(sigma)用于探索。动作从正态分布N(mu, sigma^2)中采样:

function action = chooseActionContinuous(actorNet, state, sigma) state = dlarray(state, 'CB'); mu = predict(actorNet, state); mu = extractdata(mu); % 添加探索噪声 action = mu + sigma .* randn(size(mu)); % 通常需要对动作进行裁剪,以符合环境限制 action = max(min(action, actionHigh), actionLow); end

这里的sigma(探索噪声)可以设置为一个衰减的 schedule,例如sigma = max(sigma_min, sigma * decay_rate),随着训练回合数增加而减小,实现从“大胆探索”到“精细利用”的过渡。

3.3 核心更新逻辑:策略梯度与价值更新

这是整个项目最精华、也是最容易出错的部分,位于agent/update.m或类似文件中。我们以最常见的使用TD误差作为优势函数估计的更新方式为例。

第一步:计算TD误差TD误差δ是Critic网络更新的目标,也是Actor策略梯度的权重。

function td_error = computeTDError(criticNet, state, reward, nextState, isDone, gamma) % gamma: 折扣因子 V = predict(criticNet, dlarray(state, 'CB')); % 当前状态价值 V_next = predict(criticNet, dlarray(nextState, 'CB')); % 下一状态价值 if isDone target = reward; % 回合结束,下一状态价值为0 else target = reward + gamma * extractdata(V_next); end td_error = target - extractdata(V); % TD误差 = 目标 - 当前估计 end

td_error可以理解为“惊喜度”。如果td_error > 0,说明实际回报比Critic预测的要好,这个(state, action)组合应该被加强;反之则应该被削弱。

第二步:更新Critic网络(价值函数)Critic的目标是让自己的预测V(s)尽可能接近真实的目标值target。因此,其损失函数是均方误差(MSE):

function [criticGradients, criticLoss] = computeCriticGradients(criticNet, state, target) V = predict(criticNet, dlarray(state, 'CB')); criticLoss = mse(V, target); % 计算损失 criticGradients = dlgradient(criticLoss, criticNet.Learnables); % 自动求梯度 end

然后,在更新函数中,我们会用这个梯度,结合优化器(如Adam)来更新Critic网络的参数。

第三步:更新Actor网络(策略)Actor的更新目标是最大化期望回报。策略梯度定理告诉我们,梯度可以通过对数概率乘以优势函数来估计。这里我们用TD误差作为优势函数的无偏估计。

function [actorGradients, actorLoss] = computeActorGradients(actorNet, state, action, td_error) % 注意:此函数针对离散动作空间。连续空间需计算对数概率密度。 prob = predict(actorNet, dlarray(state, 'CB')); % 得到动作概率 logProb = log(prob(action)); % 计算所选动作的对数概率 % 策略梯度损失: -logProb * advantage (td_error) % 因为优化器默认最小化损失,所以我们用负号将最大化问题转为最小化 actorLoss = -logProb * td_error; actorGradients = dlgradient(actorLoss, actorNet.Learnables); end

这里有一个极其重要的细节:我们计算的是所选动作action的对数概率,而不是所有动作。梯度只会沿着实际执行的动作方向更新。td_error作为标量权重,决定了这次更新的幅度和方向。

注意事项:梯度计算与dlarray:Matlab的自动微分要求所有计算都在dlarray类型的数据上进行。确保你的state,action等在输入网络前被正确转换为dlarray。另外,dlgradient的调用必须在一个dlfeval包裹的函数内,或者直接像上面那样在定义了损失函数后调用。一个常见的错误是试图对已经从dlarray中extractdata出来的普通数值计算梯度,这会导致错误。

第四步:参数更新计算出梯度后,使用优化器进行更新。以Adam为例:

% 假设 actorGradients, criticGradients 已计算,actorState, criticState 是优化器状态 [actorNet, actorState] = adamupdate(actorNet, actorGradients, actorState, actorLearnRate); [criticNet, criticState] = adamupdate(criticNet, criticGradients, criticState, criticLearnRate);

至此,一次完整的Actor-Critic更新就完成了。这个过程在每个时间步(或每N步)重复进行。

4. 完整训练流程与参数配置实战

4.1 主训练循环(main.m)的逐行解读

理解了核心更新逻辑后,我们来看如何将它们组织成一个完整的训练流程。主脚本main.m通常包含以下步骤:

1. 初始化设置:

clear; close all; clc; % 经典清空命令 addpath(genpath('.')); % 添加所有子文件夹到路径 % 超参数配置(这是你需要反复调整的地方) numEpisodes = 1000; % 训练回合数 maxStepsPerEpisode = 500; % 每回合最大步数 gamma = 0.99; % 折扣因子,越接近1越重视远期回报 actorLearnRate = 1e-4; % Actor网络学习率,通常比Critic小 criticLearnRate = 1e-3; % Critic网络学习率 explorationSigma = 0.5; % 连续动作的初始探索噪声(离散动作不需要) sigmaDecay = 0.995; % 探索噪声衰减率 sigmaMin = 0.01; % 最小探索噪声

2. 创建环境和智能体:

env = createEnv(); % 初始化环境,如CartPole stateDim = env.observationDimension; actionDim = env.actionDimension; % 初始化Actor和Critic网络 actorNet = createActorNetwork(stateDim, actionDim); criticNet = createCriticNetwork(stateDim); % 初始化优化器状态(对于Adam,需要为每个可学习参数维护动量和方差) actorOptState = struct; % 实际项目中,这里需要初始化Adam状态结构体 criticOptState = struct; % 通常需要写一个初始化函数来正确设置这些状态。

3. 训练主循环:

episodeRewards = zeros(numEpisodes, 1); % 记录每回合总奖励 for episode = 1:numEpisodes state = env.reset(); % 重置环境,获取初始状态 episodeReward = 0; isDone = false; stepCount = 0; while ~isDone && stepCount < maxStepsPerEpisode % 1. 选择动作 action = chooseAction(actorNet, state, explorationSigma); % 包含探索 % 2. 执行动作,与环境交互 [nextState, reward, isDone] = env.step(action); episodeReward = episodeReward + reward; % 3. 计算TD误差(需要Critic网络预测V(s)和V(s')) tdError = computeTDError(criticNet, state, reward, nextState, isDone, gamma); % 4. 更新Critic网络 target = reward + gamma * predict(criticNet, dlarray(nextState, 'CB')) * (~isDone); [criticGradients, ~] = computeCriticGradients(criticNet, state, target); [criticNet, criticOptState] = adamupdate(criticNet, criticGradients, criticOptState, criticLearnRate); % 5. 更新Actor网络 [actorGradients, ~] = computeActorGradients(actorNet, state, action, tdError); [actorNet, actorOptState] = adamupdate(actorNet, actorGradients, actorOptState, actorLearnRate); % 6. 转移到下一个状态 state = nextState; stepCount = stepCount + 1; end % 回合结束,记录奖励并衰减探索率 episodeRewards(episode) = episodeReward; explorationSigma = max(sigmaMin, explorationSigma * sigmaDecay); % 每100回合打印一次进度 if mod(episode, 100) == 0 fprintf('Episode %d, Total Reward: %.2f, Sigma: %.3f\n', ... episode, episodeReward, explorationSigma); end end

4. 保存与可视化:

save('trained_agent.mat', 'actorNet', 'criticNet', 'episodeRewards'); plotResults(episodeRewards); % 自定义函数,绘制奖励曲线

4.2 超参数调优经验与策略

超参数是RL训练中的“玄学”,但也有一些经验法则可循。以下是我在调试这个Matlab Actor-Critic项目时积累的一些心得:

超参数典型范围/值影响与调整策略常见坑与现象
学习率 (actorLearnRate, criticLearnRate)Actor: 1e-5 到 1e-4
Critic: 1e-4 到 1e-3
Critic学习率通常比Actor大,因为价值函数需要更快地收敛以提供稳定的基线。如果奖励曲线剧烈震荡或发散,首先尝试同时调小两个学习率(例如除以10)。学习率过大:奖励曲线爆炸(变成NaN)或剧烈上下波动,无法收敛。
学习率过小:学习速度极慢,奖励曲线几乎是一条平线。
折扣因子 (gamma)0.9 到 0.999决定了智能体对未来奖励的重视程度。环境任务如果是短视的(如CartPole,尽快平衡),gamma可以设小(0.95-0.99)。如果是长远的(如下围棋),gamma需要接近1(0.99-0.999)。gamma过大(接近1):智能体过于“理想化”,可能难以学习,因为远期奖励不确定性高。
gamma过小:智能体变得“短视”,可能无法学会需要多步规划的策略。
探索噪声 (explorationSigma)初始: 0.1-1.0
衰减: 0.99-0.999
对于连续动作控制至关重要。初始值要足够大以鼓励探索,但太大会导致动作随机,无法学习。衰减率决定了探索收敛到利用的速度。一个策略是:观察奖励曲线,在奖励开始稳定上升后,再让sigma衰减。初始sigma太小:智能体被困在初始策略附近,无法探索到更优区域。
衰减太快:智能体过早停止探索,可能收敛到局部最优。
不衰减:策略始终带有噪声,无法达到最优性能。
网络结构隐藏层: [128, 64] 或 [256, 128]从简单结构开始。对于简单环境,复杂的网络容易过拟合且训练慢。如果学习停滞,可以尝试增加一层或增加每层神经元数量。激活函数通常用ReLU,输出层根据动作空间决定。网络太复杂:训练速度慢,且在小环境中可能无法有效学习。
网络太简单:表达能力不足,无法拟合复杂的价值函数或策略。
批量大小 (如果使用)32, 64, 128如果实现了经验回放或批量更新,批量大小会影响梯度估计的方差。较小的批量(如32)更新更频繁但噪声大;较大的批量(如256)更稳定但计算开销大,且可能陷入局部最优。在基础on-policy Actor-Critic中,常为1(单步更新)。若要引入批量更新,需配合重要性采样等技术。

一个实用的调参流程:

  1. 先固定一组保守的基准参数:例如,actor_lr=3e-4, critic_lr=1e-3, gamma=0.99。
  2. 运行少量回合(如200),观察奖励曲线趋势。如果奖励完全不增长,检查代码bug(见下一章)。
  3. 如果学习不稳定(震荡),尝试将两个学习率同时除以3或5。
  4. 如果学习速度慢但稳定,可以尝试稍微增大学习率,或增加网络容量。
  5. 调整gamma:如果任务需要多步规划,适当增大gamma;如果任务即时奖励密集,可以稍减小gamma。
  6. 耐心:RL训练具有随机性,同一组参数多跑几次,看平均趋势,不要因为一次不好的结果就否定一组参数。

4.3 训练过程监控与可视化

“黑箱”训练是RL调试的噩梦。一个好的项目应该提供训练过程的监控。除了记录每回合的总奖励episodeRewards,还可以记录更多信息:

  • 平均TD误差:反映Critic网络预测的准确度。理想情况下,TD误差的绝对值应该随着训练逐渐减小并趋于零附近波动。
  • 策略熵:对于离散动作,可以计算策略概率的熵H = -sum(p * log(p))。熵值高表示策略随机(探索强),熵值低表示策略确定(利用强)。观察熵随训练衰减的过程,可以验证探索策略是否按预期工作。
  • 梯度范数:记录Actor和Critic网络梯度的L2范数。如果梯度爆炸(突然变得极大),说明学习率可能太高或网络结构有问题。如果梯度消失(接近0),说明学习可能停滞。

在Matlab中,可以在训练循环中添加这些计算,并定期(如每10回合)打印或保存。plotResults.m函数可以扩展,不仅绘制奖励曲线,还将这些指标以子图形式展示出来,这对分析训练动态非常有帮助。

% 在训练循环内添加记录 avgTDError = mean(abs(tdErrorBuffer)); % 假设tdErrorBuffer存储了最近一些步的TD误差 policyEntropy = -sum(prob .* log(prob + 1e-10)); % 防止log(0) if mod(episode, 10) == 0 fprintf('Ep %d, Reward: %.1f, Avg|TD|: %.4f, Entropy: %.4f\n', ... episode, episodeReward, avgTDError, policyEntropy); end

5. 常见问题排查与实战调试技巧

即使有了清晰的代码,在实际运行中你依然会遇到各种问题。下面是我在复现和修改此类项目时遇到的典型问题及解决方法。

5.1 训练不收敛或奖励曲线异常

这是最常见的问题。我们可以通过奖励曲线的形态来初步判断问题所在。

现象1:奖励始终在最低值附近徘徊,没有上升趋势。

  • 可能原因1:探索不足。智能体一开始就陷入了某个局部策略,且没有足够的噪声跳出。检查:chooseAction函数是否真的进行了随机采样(离散)或添加了噪声(连续)?初始的explorationSigma是否太小?对于离散动作,可以打印前几个回合的动作概率,看看是否均匀。
  • 可能原因2:学习率过低或网络初始化不当。梯度更新太小,网络参数几乎不变。检查:学习率是否设置得过小(如1e-6)?可以尝试暂时调大学习率(如1e-3)看是否有任何变化。同时,检查网络输出,在初始化后,输入一个典型状态,看输出是否合理(例如,离散动作概率不应全是0或1)。
  • 可能原因3:奖励函数设计问题。环境返回的奖励始终是0或负值,且没有提供足够的学习信号。检查:在训练循环中打印每一步的reward,看看是否有正奖励出现。也许你需要重新设计奖励函数,加入稀疏奖励的稠密化技巧。

现象2:奖励曲线剧烈震荡,时而很高,时而崩盘。

  • 可能原因1:学习率过高。这是最可能的原因。梯度更新步伐太大,导致策略“冲过头”,从一个好策略直接跳到一个极差的策略。解决方案:显著降低Actor和Critic的学习率(例如,同时除以10)。
  • 可能原因2:Critic网络不稳定。如果Critic的价值估计不准,它给Actor提供的TD误差信号就是错误的,会导致策略被误导。解决方案:可以尝试降低Critic的学习率,或者使用目标网络(Target Network)。这是DQN中的经典技巧,也可以用于Actor-Critic。即为Critic创建一个结构相同的目标网络,其参数缓慢更新(θ_target = τ * θ + (1-τ) * θ_target,τ很小,如0.005),用于计算TD目标,可以大幅稳定训练。在这个Matlab项目中,你可以尝试实现它。
  • 可能原因3:批量更新与on-policy的冲突。如果你错误地使用了旧经验(off-policy)来更新当前的on-policy算法,由于数据分布不一致,会导致训练不稳定。确保:你用于更新的(s,a,r,s')数据是由当前策略在当前回合中产生的。

现象3:奖励曲线先上升后下降,或收敛到一个次优解。

  • 可能原因:探索衰减过快。sigmaDecay太大,导致智能体过早地停止了探索,陷入了一个局部最优解。解决方案:降低衰减率(如从0.995改为0.999),或者采用更复杂的探索策略,如根据策略的确定性程度(熵)来动态调整探索率。

5.2 代码运行错误与Matlab特定问题

错误1:dlarray相关错误,如“不支持的操作”或梯度为NaN。

  • 检查点:
    1. 数据类型:确保输入到predict和dlgradient的所有数据都是dlarray。state,target等都需要转换。
    2. 计算图:所有导致损失的计算都必须在同一个“函数”内完成,并且用dlfeval调用,或者直接像前文示例那样在定义损失后立即调用dlgradient。不能中途用extractdata提取数据做其他计算再试图求导。
    3. 数值稳定性:在计算对数概率log(prob)时,prob可能为0,导致-Inf。务必加上一个极小值:logProb = log(prob(action) + 1e-10);。

错误2:训练一段时间后,奖励或网络参数变成NaN。

  • 根本原因:数值爆炸。可能是梯度爆炸,或者网络输出出现了极大的值。
  • 排查步骤:
    1. 梯度裁剪:在更新网络参数前,对梯度进行裁剪。Matlab的adamupdate没有内置裁剪,你需要手动实现:gradients = dlupdate(@(g) min(max(g, -gradThreshold), gradThreshold), gradients);,其中gradThreshold是一个标量(如5或10)。
    2. 网络输出裁剪/归一化:检查Critic网络输出的价值估计V(s)是否过大。可以考虑对状态输入进行归一化(减去均值,除以标准差),或者对Critic的输出进行裁剪。
    3. 奖励缩放:如果环境奖励本身数值很大或变化范围很大,可以考虑对奖励进行缩放(如除以一个常数),使其分布在[-1, 1]附近。

错误3:训练速度极慢。

  • 可能原因:
    1. 环境模拟慢:如果环境是复杂的物理仿真(如自己用Simulink建的模型),每一步env.step()都可能很耗时。考虑简化模型,或增加每步更新的数据利用率(如使用批量更新)。
    2. Matlab循环开销:Matlab的for循环在旧版本中较慢。确保你的代码是向量化的。但在RL中,由于每一步都需要顺序交互,向量化机会有限。可以尝试使用parfor并行跑多个环境实例(如果环境可复制),但这会显著增加代码复杂度。
    3. 图形渲染:如果环境每一步都渲染图形,会极大拖慢速度。在训练时,关闭渲染或大幅降低渲染频率。

5.3 性能优化与进阶修改建议

当你的基础版本能跑通后,可以考虑以下优化来提升性能或适配更复杂任务:

1. 引入优势函数标准化:在计算Actor损失时,直接使用TD误差作为优势函数A(s,a)。但不同状态下的TD误差量级可能差异很大,这会导致梯度更新方差大。一个常见的技巧是对优势函数进行标准化(减去均值,除以标准差):

advantages = [td1, td2, ...]; % 收集一个批次内的TD误差 advantages = (advantages - mean(advantages)) / (std(advantages) + 1e-8); % 标准化 actorLoss = -mean(logProbs .* advantages); % 使用标准化后的优势

这能使训练更稳定。

2. 实现并行环境采样:这是加速训练最有效的方法之一。同时运行多个独立的环境实例,收集数据,然后集中进行一批更新。这需要修改主循环结构,并处理好多个环境的状态同步。虽然实现复杂,但能数倍提升数据采集效率。

3. 替换为更先进的算法变体:这个基础Actor-Critic是A2C(同步优势演员-评论家)。你可以在此基础上修改,实现:

  • A3C(异步优势演员-评论家):多个智能体线程异步更新一个全局网络。
  • PPO(近端策略优化):在损失函数中添加一个裁剪项,限制每次策略更新的幅度,从而更稳定。其损失函数为:L = min(ratio * A, clip(ratio, 1-ε, 1+ε) * A),其中ratio = new_prob / old_prob。
  • SAC(柔性演员-评论家):一种基于最大熵的off-policy算法,在连续控制任务中表现卓越。

修改这个Matlab项目来实现这些算法是绝佳的学习过程,你需要深入理解它们与基础Actor-Critic在目标函数和更新方式上的区别。

4. 与Simulink集成:这是Matlab生态的独特优势。你可以将这个RL智能体作为控制器,部署到Simulink模型中,进行硬件在环(HIL)仿真或直接控制物理设备。这需要将训练好的Actor网络导出为function或使用coder工具生成C/C++代码,并在Simulink中通过MATLAB Function块或S-Function进行调用。这个过程能将算法研究与工程应用无缝衔接。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询