简介:本资源是面向强化学习初学者与Matlab实践者的Actor-Critic算法完整实现包,聚焦于连续/离散控制任务中的策略优化与价值评估协同训练问题,适用于高校课程设计、科研原型验证及RL算法原理理解。压缩包共10个文件,含7个核心MATLAB源码(.m)与3个备份脚本(.asv),涵盖环境仿真(simulator.m)、策略网络更新(computpi.m)、价值函数逼近(evaluate.m)、优势计算(computpsi.m)、主训练流程(AC.m)等关键模块,结构清晰、注释充分,便于分步调试与原理对照。资源仅5KB,轻量易读,无冗余依赖,可直接在MATLAB R2020a及以上版本运行。目前已有460人学习下载,读者可快速掌握Actor-Critic双网络架构设计、策略梯度更新机制、Critic均方误差损失构建,以及rl Toolbox兼容的环境交互范式,是深入理解深度强化学习中“决策+评估”解耦思想的优质入门材料。
1. 项目概述:Actor-Critic网络与Matlab实现
最近在整理硬盘时,翻出了一个老项目文件,名字就叫“actor-critic网络 Matlab.zip”。这让我想起了几年前,为了搞明白强化学习里这个经典算法,在Matlab里吭哧吭哧敲代码、调参数的日子。Actor-Critic,翻译过来就是“演员-评论家”,听起来挺文艺,但在强化学习领域,它可是一个非常核心且实用的算法框架。简单来说,它把智能体(Agent)做决策的过程分成了两部分:Actor(演员)负责根据当前环境状态,选择要执行的动作;Critic(评论家)则负责评价这个状态(或者状态-动作对)的好坏,给Actor的“表演”打分。两者相互配合,共同学习,最终目标是让智能体学会一套能获得最高长期回报的策略。
这个用Matlab实现的压缩包,本质上就是一个完整的、可运行的Actor-Critic算法工具箱。它特别适合那些对强化学习理论有一定了解,但希望有一个清晰、可修改的代码实现来加深理解,或者想快速搭建一个原型进行算法验证的研究者、工程师和学生。Matlab环境以其强大的矩阵运算、丰富的可视化工具和相对友好的语法,成为了很多算法初学者的首选实验平台。通过这个项目,你不仅能看懂Actor-Critic是怎么工作的,更能亲手运行它、修改它,甚至用它来解决你自己的问题,比如让一个小车学会平衡、让一个机械臂学会抓取,或者在一个简单的游戏里找到最优策略。
2. Actor-Critic核心原理深度拆解
要真正用好这个Matlab工具包,不能只停留在“跑通代码”的层面,必须深入理解其背后的设计思想。这能帮助你在调试时知道该动哪里,在扩展时知道如何下手。
2.1 算法框架与核心思想
Actor-Critic属于策略梯度(Policy Gradient)算法家族,但它巧妙地引入了价值函数作为基线(Baseline),以降低训练方差,从而比单纯的REINFORCE算法更稳定、收敛更快。它的核心运作机制是一个紧密耦合的闭环:
- 交互与采样:智能体(由Actor和Critic组成)与环境交互。在时刻t,智能体观察到环境状态
s_t。 - Actor决策:Actor网络(通常是一个神经网络)接收
s_t,输出一个动作的概率分布π(a|s_t),然后依此分布采样得到实际执行的动作a_t。 - 环境反馈:执行
a_t后,环境转移到新状态s_{t+1},并给出即时奖励r_t。 - Critic评价:Critic网络(另一个神经网络)负责评估状态的价值。它接收
s_t(或(s_t, a_t)),输出一个标量值V(s_t),代表处于状态s_t的长期期望回报。 - 计算优势函数:这是关键一步。我们计算时序差分误差(Temporal-Difference Error, TD-Error):
δ_t = r_t + γ * V(s_{t+1}) - V(s_t)。其中γ是折扣因子。这个δ_t可以理解为实际获得的回报(r_t + γV(s_{t+1}))与Critic之前预测的回报(V(s_t))之间的差异,也就是优势(Advantage)的一个无偏估计。它量化了在状态s_t下执行动作a_t比“平均表现”好多少(如果δ_t > 0)或差多少(如果δ_t < 0)。 - 网络更新:
- Critic更新:目标是让Critic的预测更准确。使用
δ_t作为损失信号,通过梯度下降最小化δ_t^2(均方误差),更新Critic网络的参数。这相当于让Critic学会更精准地评价每个状态。 - Actor更新:目标是让Actor的策略变得更好。利用计算出的优势
δ_t来更新Actor网络。如果δ_t为正,说明当前(s_t, a_t)是好的,那么就通过梯度上升,微调Actor网络参数,使得未来在类似状态s_t下,选择动作a_t的概率增加;反之,如果δ_t为负,则降低选择该动作的概率。
- Critic更新:目标是让Critic的预测更准确。使用
这个“Actor行动,Critic打分,根据分数调整行动”的过程,非常像学徒(Actor)在导师(Critic)的指导下学习。导师不直接告诉学徒该怎么做,而是对他的每次尝试给出“好”或“不好”的评价,学徒根据这些评价逐渐调整自己的行为模式。
2.2 与其他强化学习算法的对比
理解Actor-Critic的定位,有助于你在不同场景下做出选择。
- 与值函数方法(如Q-Learning、DQN)对比:值函数方法(如Deep Q-Network, DQN)直接学习一个动作价值函数Q(s, a),然后通过选择Q值最大的动作来决策。这类方法通常用于离散动作空间。而Actor-Critic是直接学习策略本身(一个从状态到动作概率分布的映射),因此天然支持连续动作空间。你不需要在连续空间中寻找最大Q值(这是一个优化问题),而是由Actor网络直接输出连续的动作值(如速度、力)。
- 与纯策略梯度方法(如REINFORCE)对比:REINFORCE算法也直接优化策略,但它使用从一幕(episode)开始到结束的完整回报来更新策略,方差很大,导致训练不稳定且缓慢。Actor-Critic引入Critic提供的价值函数作为基线,用TD-Error(单步或几步的回报)替代完整回报,极大地降低了方差,实现了更稳定、更快的在线学习(无需等待一幕结束)。
- 与先进算法(如PPO、SAC)的关系:现代深度强化学习的许多明星算法,如近端策略优化(PPO)、软演员-评论家(SAC),都是在Actor-Critic框架基础上发展而来的。它们通过引入重要性采样、熵正则化、裁剪等技巧,进一步提升了样本效率、稳定性和性能。因此,掌握经典的Actor-Critic,是理解这些更高级算法的基石。
3. Matlab实现的关键模块解析
打开“actor-critic网络 Matlab.zip”,你通常会看到几个核心的.m文件。我们来逐一拆解每个模块的功能和实现要点。
3.1 环境接口模块 (Environment.m或类似)
任何强化学习实验都始于环境。这个模块定义了智能体与之交互的世界。
classdef CartPoleEnv properties gravity = 9.8 masscart = 1.0 masspole = 0.1 total_mass = 1.1 length = 0.5 polemass_length = 0.05 force_mag = 10.0 tau = 0.02 % 仿真时间步长 theta_threshold_radians = 12 * pi / 180 x_threshold = 2.4 end methods function [obs, reward, done, info] = step(obj, action) % 输入:action (力的大小,连续值或离散索引) % 输出:obs (新状态), reward (即时奖励), done (是否结束), info (调试信息) % 这里实现小车和杆的物理仿真逻辑(欧拉积分) % ... % 计算奖励:通常杆子越直立、小车越居中,奖励越高 % 判断结束条件:杆子倒下或小车出界 end function obs = reset(obj) % 重置环境到初始状态(如小车居中,杆子轻微随机角度) obs = [0; 0.05*randn(); 0; 0]; % [位置, 角度, 速度, 角速度] end end end实操要点:
- 状态归一化:环境的原始状态(如位置、角度)可能数值范围差异很大。在输入神经网络前,务必进行归一化(例如,缩放到[-1, 1]或均值0方差1)。这能显著提高训练的稳定性和速度。可以在
reset和step函数返回前处理,也可以单独写一个归一化函数。 - 奖励函数设计:奖励函数是指引智能体学习的“指挥棒”。设计不当会导致学习失败或学到奇怪策略。对于倒立摆(CartPole),常见的奖励是每一步杆子直立就给+1,倒下则结束。也可以设计为
reward = 1.0 - abs(theta/theta_threshold),让奖励与角度偏差成反比,提供更平滑的梯度。 - 离散 vs 连续动作:Matlab实现可能处理离散动作(如向左/向右施力)或连续动作(输出一个[-10, 10]的力)。代码中的
action参数需要与环境期望的格式匹配。
3.2 神经网络模型定义 (createNetworks.m)
这是Actor和Critic的核心,通常使用Matlab的deepLearningToolbox来构建。
function [actorNetwork, criticNetwork] = createNetworks(stateDim, actionDim, actionMax) % Actor网络:输入状态,输出动作均值(连续)或动作概率(离散) actorLayers = [ featureInputLayer(stateDim, 'Name', 'stateInput') fullyConnectedLayer(128, 'Name', 'actorFC1') reluLayer('Name', 'actorRelu1') fullyConnectedLayer(64, 'Name', 'actorFC2') reluLayer('Name', 'actorRelu2') fullyConnectedLayer(actionDim, 'Name', 'actorOutput') % 对于连续动作,通常加一个tanh层将输出限制在[-1,1],再乘以actionMax tanhLayer('Name', 'actorTanh') scalingLayer('Name', 'actorScale', 'Scale', actionMax) ]; actorNetwork = dlnetwork(actorLayers); % Critic网络:输入状态,输出状态价值V(s) criticLayers = [ featureInputLayer(stateDim, 'Name', 'stateInput') fullyConnectedLayer(128, 'Name', 'criticFC1') reluLayer('Name', 'criticRelu1') fullyConnectedLayer(64, 'Name', 'criticFC2') reluLayer('Name', 'criticRelu2') fullyConnectedLayer(1, 'Name', 'criticOutput') % 输出单个标量值 ]; criticNetwork = dlnetwork(criticLayers); end注意事项与心得:
- 网络结构不宜过深:对于大多数经典控制任务(如倒立摆、月球着陆器),2-3个隐藏层,每层64-256个神经元通常足够。过深的网络容易过拟合,且训练更慢。
- 激活函数选择:隐藏层常用ReLU或其变种(Leaky ReLU),因为它们能缓解梯度消失问题,计算也快。Actor的输出层,对于连续动作,使用
tanh将输出约束在有限范围内是关键;对于离散动作,则使用softmax层输出概率分布。 - 参数初始化:使用
deepLearningToolbox的默认初始化(如Glorot)通常可以。但对于Actor的输出层,有时将其初始权重设小一点(如乘以0.01),可以让初始策略接近随机,有利于探索。 - 使用
dlnetwork对象:Matlab推荐使用dlnetwork来定义自定义训练循环的网络,它比layerGraph更灵活,便于计算自定义损失函数的梯度。
3.3 经验回放缓冲区 (ReplayBuffer.m)
为了打破数据间的时序相关性,提高数据利用率,几乎所有的现代深度强化学习算法都会使用经验回放缓冲区(Replay Buffer)。
classdef ReplayBuffer < handle properties capacity % 缓冲区容量 buffer % 存储经验的单元数组或结构体数组 ptr % 当前写入指针 size % 当前缓冲区大小 batch_size % 采样批次大小 end methods function obj = ReplayBuffer(capacity, stateDim, actionDim) obj.capacity = capacity; obj.buffer.state = zeros(stateDim, capacity); obj.buffer.action = zeros(actionDim, capacity); obj.buffer.reward = zeros(1, capacity); obj.buffer.next_state = zeros(stateDim, capacity); obj.buffer.done = false(1, capacity); obj.ptr = 1; obj.size = 0; end function store(obj, state, action, reward, next_state, done) % 存储一条经验 (s, a, r, s', done) idx = obj.ptr; obj.buffer.state(:, idx) = state; obj.buffer.action(:, idx) = action; obj.buffer.reward(idx) = reward; obj.buffer.next_state(:, idx) = next_state; obj.buffer.done(idx) = done; obj.ptr = mod(idx, obj.capacity) + 1; obj.size = min(obj.size + 1, obj.capacity); end function [states, actions, rewards, next_states, dones] = sample(obj, batch_size) % 随机采样一批经验 if obj.size < batch_size error('Buffer does not have enough samples.'); end indices = randi(obj.size, [1, batch_size]); states = obj.buffer.state(:, indices); actions = obj.buffer.action(:, indices); rewards = obj.buffer.reward(indices); next_states = obj.buffer.next_state(:, indices); dones = obj.buffer.done(indices); end end end核心技巧:
- 缓冲区容量:容量太小,数据快速被覆盖,学不到长期依赖;容量太大,占用内存多,且旧数据可能来自性能很差的策略,对当前学习无益。对于中等复杂度的任务,
1e5到1e6是个不错的起点。 - 批次采样:更新网络时,从缓冲区随机采样一个批次(如128、256)的数据,而不是使用最近的一条经验。这种随机化能有效减少梯度更新间的相关性,使训练更稳定。
- 优先经验回放(PER):这是一个高级技巧。基本的均匀采样假设所有经验同等重要。但事实上,那些TD-Error大的经验(即Critic预测误差大,或Actor做出了令人“意外”的好/坏决策)可能包含更多信息。PER会根据TD-Error的大小给每条经验赋予不同的采样优先级。在Matlab中实现PER稍复杂,需要维护一个求和树(SumTree)数据结构来高效采样。在初始版本中,可以先使用均匀采样。
3.4 核心训练循环 (trainAC.m)
这是所有模块组装起来的地方,是算法的心脏。
function [actor, critic, episode_rewards] = trainAC(env, actor, critic, params) % 参数解包 num_episodes = params.num_episodes; max_steps = params.max_steps; gamma = params.gamma; actor_lr = params.actor_lr; critic_lr = params.critic_lr; batch_size = params.batch_size; % 初始化回放缓冲区 buffer = ReplayBuffer(params.buffer_capacity, env.obs_dim, env.act_dim); % 优化器 actor_optimizer = adamOptimizer(actor_lr); critic_optimizer = adamOptimizer(critic_lr); episode_rewards = zeros(1, num_episodes); for ep = 1:num_episodes state = env.reset(); ep_reward = 0; done = false; step = 0; while ~done && step < max_steps % 1. 选择动作 (带探索) action = selectAction(actor, state, params.exploration_noise); % 2. 与环境交互 [next_state, reward, done, ~] = env.step(action); % 3. 存储经验 buffer.store(state, action, reward, next_state, done); state = next_state; ep_reward = ep_reward + reward; step = step + 1; % 4. 如果缓冲区有足够样本,则更新网络 if buffer.size >= batch_size [states, actions, rewards, next_states, dones] = buffer.sample(batch_size); % 转换为 dlarray 以支持自动微分 states = dlarray(states, 'CB'); % Channel x Batch actions = dlarray(actions, 'CB'); next_states = dlarray(next_states, 'CB'); % 5. 更新 Critic [critic_loss, critic_grad] = dlfeval(@criticLoss, critic, states, rewards, next_states, dones, gamma); critic = updateNetwork(critic, critic_optimizer, critic_grad); % 6. 更新 Actor [actor_loss, actor_grad] = dlfeval(@actorLoss, actor, critic, states, actions, gamma); actor = updateNetwork(actor, actor_optimizer, actor_grad); end end episode_rewards(ep) = ep_reward; % 每隔一段时间打印日志或绘图 if mod(ep, 100) == 0 fprintf('Episode %d, Reward: %.2f, Steps: %d\n', ep, ep_reward, step); plot(episode_rewards(1:ep)); drawnow; end end end关键函数解析:
selectAction: 此函数基于Actor网络输出选择动作。对于连续动作,通常在网络输出的均值上添加探索噪声,最常见的是奥恩斯坦-乌伦贝克(OU)噪声或简单的高斯噪声。OU噪声具有惯性,适合惯性系统(如机械控制),而高斯噪声更简单。对于离散动作,通常采用ε-greedy策略或直接按概率采样。criticLoss: Critic的损失函数是TD-Error的均方值。function loss = criticLoss(criticNet, states, rewards, next_states, dones, gamma) V = forward(criticNet, states); % 当前状态价值 V_next = forward(criticNet, next_states); % 下一状态价值 % 对于终止状态,下一状态价值为0 V_next(dones) = 0; % TD目标 target = rewards + gamma * extractdata(V_next); % 均方误差损失 loss = mse(V, target); endactorLoss: Actor的损失函数是策略梯度。对于离散动作,常用交叉熵损失加权优势;对于连续动作(假设输出是高斯分布的均值),损失是负的对数概率加权优势。function loss = actorLoss(actorNet, criticNet, states, actions, gamma) % 假设连续动作,Actor输出动作均值mu mu = forward(actorNet, states); % 计算动作的对数概率(假设固定方差,或由另一个网络头输出) log_probs = -0.5 * sum((actions - mu).^2, 1); % 忽略常数项 % 计算优势A(s,a) ≈ δ V = forward(criticNet, states); % 注意:这里简化了,实际需要next_states来计算TD error。 % 更严谨的做法是在存储经验时也存储TD-error,或在这里重新计算。 % 这里用负的Critic值作为简单的优势估计(鼓励去到价值高的状态)。 advantages = -extractdata(V); % 这是一个简化示例,实际应用TD-error % 策略梯度损失: -log_prob * advantage loss = -mean(log_probs .* advantages); end注意:上面的
actorLoss函数是一个高度简化的示例,用于说明概念。在实际的Actor-Critic中,优势函数advantages应该用TD-Errorδ来计算,这需要rewards和next_states。通常我们在trainAC主循环中先计算好TD-Error,然后将其作为额外参数传入actorLoss,或者直接使用dlfeval计算包含Critic前向传播的完整损失。
4. 实战调参与训练技巧
有了代码框架,成功与否很大程度上取决于超参数设置和训练技巧。这里分享一些从无数次失败中总结出的经验。
4.1 超参数设置指南
没有一套放之四海而皆准的超参数,但以下范围可以作为你调试的起点:
| 超参数 | 推荐范围/值 | 作用与影响 |
|---|---|---|
| 学习率 (Actor/Critic) | 1e-4到1e-3 | 控制参数更新步长。Critic的学习率通常可以比Actor稍大(如3e-4vs1e-4),因为价值函数通常比策略更容易学习。太大导致震荡,太小收敛慢。 |
| 折扣因子 (γ) | 0.95到0.99 | 衡量未来奖励的重要性。越接近1,智能体越有远见。对于回合制任务(如游戏通关),可以设高一些(0.99);对于连续控制且每一步都重要的任务,可以设低一些(0.95)。 |
| 回放缓冲区容量 | 1e5到1e6 | 存储经验的数量。应远大于一个回合的步数。 |
| 批次大小 (Batch Size) | 64,128,256 | 每次更新时从缓冲区采样的经验数量。太小噪声大,太大计算慢且容易过拟合。GPU内存允许下,可以尝试256。 |
| 探索噪声 | 高斯噪声:sigma=0.1~0.3OU噪声: theta=0.15, sigma=0.2 | 控制探索程度。开始时可以大一些,随着训练进行可以线性衰减(sigma = max(0.01, sigma * 0.995))。OU噪声的theta控制回归均值速度,sigma控制噪声强度。 |
| 网络结构 | [128, 64]或[256, 128] | 隐藏层神经元数量。任务越复杂,网络可以适当加深加宽,但也要防止过拟合。 |
| 优化器 | Adam(默认参数) | 最常用的自适应学习率优化器。对于RL,通常不需要调整Adam的beta1和beta2。 |
调试顺序建议:如果训练不收敛,首先检查学习率,这是最常见的问题源。可以先尝试调低一个数量级。其次,检查奖励函数设计是否合理,智能体是否能轻易获得正向奖励。然后,调整探索噪声的大小和衰减策略。最后,再考虑修改网络结构。
4.2 训练过程监控与可视化
“黑箱”训练是RL调试的噩梦。必须建立有效的监控手段。
回合奖励曲线:这是最核心的指标。绘制每个训练回合获得的总奖励。你期望看到曲线总体呈上升趋势,并最终稳定在一个较高的水平。如果曲线剧烈震荡或持续下降,说明训练不稳定。
% 在训练循环内 if mod(ep, 10) == 0 moving_avg = movmean(episode_rewards(max(1, ep-100):ep), 100); plot(1:ep, episode_rewards(1:ep), 'b.', 1:ep, moving_avg, 'r-', 'LineWidth', 2); xlabel('Episode'); ylabel('Total Reward'); legend('Raw', 'Moving Avg (100)'); drawnow; endCritic损失曲线:Critic的损失(TD-Error的均方)应该随着训练逐渐下降并趋于平稳。如果损失爆炸(变成NaN),几乎肯定是学习率太高或网络结构有问题。
动作与状态分布:定期输出Actor网络在某个测试状态下的动作分布,或者记录智能体执行动作的均值和方差。这有助于观察探索是否充分,策略是否收敛到某个确定模式。
实时渲染:对于有图形界面的环境(如CartPole的Matlab动画),可以每隔N个回合渲染一次,直观地看智能体的表现。但注意渲染会大幅降低训练速度。
4.3 探索与利用的平衡
这是强化学习的根本挑战。在Actor-Critic中,主要通过在Actor的输出上添加噪声来实现探索。
- 高斯噪声:简单直接,
action = mu + sigma * randn(size(mu))。sigma(标准差)的大小直接控制探索强度。可以设置一个衰减计划,让sigma随着训练回合数增加而线性或指数衰减,从而实现从“广泛探索”到“精细利用”的过渡。 - 奥恩斯坦-乌伦贝克(OU)噪声:更适合连续控制任务,特别是具有惯性的物理系统。它的噪声具有相关性,不会像高斯噪声那样频繁地正负跳跃,动作更平滑。其更新公式为:
其中dx = theta * (mu - x) + sigma * dWx是噪声状态,theta是回归速度,sigma是噪声强度,dW是维纳过程(可近似为高斯随机数)。在Matlab中需要维护一个噪声状态变量。
个人心得:对于初学者,从高斯噪声+线性衰减开始是最稳妥的。先设置一个较大的初始sigma(如0.5),每回合衰减一点点(如乘以0.995),并设置一个下限(如0.01)。观察训练曲线,如果奖励很早就停滞不前,可能是探索不够(初始sigma太小或衰减太快);如果奖励曲线一直像“心电图”一样乱跳,可能是探索太强(sigma太大),策略无法稳定。
5. 常见问题排查与解决方案
在实际运行Matlab Actor-Critic代码时,你几乎一定会遇到下面这些问题。这里我把踩过的坑和解决方法整理出来。
5.1 训练不收敛,奖励曲线毫无起色
这是最令人沮丧的情况。请按以下清单逐一排查:
- 检查环境与奖励:首先,写一个简单的随机策略(
action = rand()*2-1)在环境中运行几十个回合,看看平均奖励是多少。如果随机策略的奖励都比你训练后的高,那肯定是算法或代码出了问题。其次,确认你的奖励函数是否正确。智能体是否有可能获得正向奖励?奖励是否过于稀疏? - 检查学习率:这是头号嫌疑犯!立刻将Actor和Critic的学习率同时降低一个数量级(例如从
1e-3降到1e-4)再试。RL对学习率非常敏感,过高会导致梯度更新步伐太大,在优化地形里“跳来跳去”甚至发散。 - 检查梯度:在更新网络前,打印出梯度(
actor_grad,critic_grad)的范数(norm)。如果梯度是NaN或无限大,说明出现了数值不稳定(可能是除零、指数爆炸等)。如果梯度范数非常小(如1e-8),说明学习信号太弱,可能网络结构或损失函数有误。 - 检查探索:初始阶段,智能体是否在进行有效的探索?可以打印出前几个回合的动作值,看看它们是否在合理范围内随机变化。如果动作几乎不变,可能是探索噪声
sigma设得太小,或者Actor网络输出被激活函数(如tanh)饱和了。 - 检查网络输出:确保Actor网络的最终输出层有正确的激活函数。对于连续动作空间,
tanh层是必须的,它将输出限制在[-1,1],然后你需要乘以一个缩放系数(actionMax)来匹配环境的动作范围。如果忘记加tanh,网络可能输出非常大的值,导致环境产生异常状态。
5.2 训练初期表现尚可,随后突然崩溃(“遗忘”现象)
智能体先学到一点东西,然后奖励突然断崖式下跌,之后再也回不来。
- 回放缓冲区污染:这是主要原因。当智能体策略改进后,它会产生高质量的经验存入缓冲区。但如果缓冲区容量有限,这些好经验很快会被后期策略探索阶段产生的、质量较低的经验覆盖。当网络从混合了新旧好坏经验的缓冲区中学习时,可能会被“带偏”。解决方案:增大缓冲区容量,或者实现优先经验回放(PER),让算法更关注TD-error大的经验(通常是那些“ surprising ”的好经验或坏经验)。
- 学习率未衰减:在训练后期,策略接近最优,需要更精细的调整。如果学习率保持不变,一次大的梯度更新可能会让策略“跳”出最优区域。可以考虑设置一个学习率衰减计划(如每N个回合乘以0.9)。
- 探索噪声衰减过快:如果探索噪声衰减得太快,智能体过早地停止探索,可能会陷入一个局部最优解,一旦环境有微小扰动或由于参数更新偶然跳出该区域,就可能无法找回原来的好策略。可以放缓噪声衰减速度,或者保留一个很小的基础噪声。
5.3 收敛速度慢,训练耗时过长
- 批次大小与更新频率:尝试增大批次大小(如从64到256),这可以使梯度估计更稳定,但也会增加每次更新的计算量。另外,可以尝试每收集K步(如4步或5步)经验就更新一次网络,而不是每一步都更新,这能提高数据效率。
- 网络结构过于复杂:对于简单任务(如CartPole),过大的网络(如好几层,每层几百个神经元)不仅不会提升性能,反而会因为参数过多、需要调整的维度大增而拖慢收敛速度。尝试简化网络(如两层,每层64个神经元)。
- 使用GPU加速:确保你的Matlab版本支持GPU计算,并且正确地将数据和网络转移到GPU上。使用
dlarray和dlnetwork会自动利用GPU(如果可用)。在代码开始处使用gpuDevice(1)可以检查和选择GPU。 - 向量化操作:确保你的经验回放缓冲区的存储和采样操作是向量化的,避免在循环中进行单个元素的赋值,这能在Matlab中带来巨大的速度提升。
5.4 Matlab特有的数值与性能问题
- 数据类型转换:
dlarray和普通Matlab数值数组(double,single)之间的转换需要注意。使用extractdata(gather(...))来从dlarray中提取数值进行计算(如记录奖励)。自动微分要求前向传播的输入是dlarray。 - 内存管理:经验回放缓冲区会占用大量内存。如果任务步数很长(如数万步),注意不要将整个训练过程中的所有状态/动作都存储在内存中。使用固定容量的循环缓冲区。对于图像输入的状态,考虑存储压缩后的数据或使用专门的存储方式。
- 并行化:Matlab的
parfor可以用于并行运行多个环境实例来收集数据,这能显著加快数据采集速度。但需要注意线程安全,每个worker最好有自己独立的环境实例和随机数种子。网络更新仍需在客户端进行。
最后,保持耐心。强化学习训练本身具有随机性,即使超参数完全正确,两次运行的结果也可能有差异。通常需要多次运行(如5-10次)并取平均性能来评估一个算法或一组参数的好坏。这个Matlab实现的Actor-Critic项目是一个绝佳的学习和实验平台,通过亲手调试这些“旋钮”,你会对强化学习如何工作有更深刻、更直观的理解。
本文还有配套的精品资源,点击获取