倒立摆系统是控制理论中经典的欠驱动非线性系统,常被用作验证各种控制算法的测试平台。这个项目要实现的是通过Q-Learning强化学习算法,让智能体(小车)学会自主控制倒立摆保持平衡。相比传统PID控制,强化学习不需要精确的数学模型,而是通过试错来学习最优策略。
我在实验室第一次接触倒立摆时,就被它的"反直觉"特性吸引——要让上方的摆杆不倒,需要小车左右移动来补偿。传统控制方法需要精确建模摩擦力、转动惯量等参数,而实际系统中这些参数往往难以准确获取。这正是强化学习的用武之地。
倒立摆系统可以用以下非线性微分方程描述:
code复制θ'' = (mgl sinθ - mlθ'² cosθ - bθ') / (I + ml²)
x'' = (F - mlθ'' cosθ + mlθ'² sinθ - μx') / (M + m)
其中:
Q-Learning需要离散的状态空间。我们将连续状态变量离散化为有限区间:
这样总状态数为10×10×10×10=10,000。动作空间离散为3个:向左力(-10N)、零力、向右力(+10N)。
Q表是一个10,000×3的矩阵,初始值设为零。更新规则为:
code复制Q(s,a) ← Q(s,a) + α[r + γ max Q(s',a') - Q(s,a)]
参数设置经验:
奖励函数是强化学习成功的关键。经过多次实验,我采用以下分段奖励:
matlab复制function reward = getReward(theta, theta_dot, x, x_dot)
if abs(theta) > pi/6 || abs(x) > 2.4
reward = -100; % 失败惩罚
elseif abs(theta) < 0.1 && abs(theta_dot) < 0.5 && abs(x) < 0.5
reward = +10; % 理想状态奖励
else
reward = -abs(theta) - 0.1*abs(theta_dot) - 0.01*abs(x);
end
end
注意:初期尝试过纯负奖励(仅惩罚失败),但学习效率极低。加入正向引导奖励后收敛速度明显提升。
matlab复制% 初始化参数
num_episodes = 5000;
max_steps = 500;
alpha = 0.2; gamma = 0.9; epsilon = 0.3;
% 离散化设置
theta_bins = linspace(-pi/6, pi/6, 10);
theta_dot_bins = linspace(-3, 3, 10);
x_bins = linspace(-2.4, 2.4, 10);
x_dot_bins = linspace(-1, 1, 10);
% Q表初始化
Q = zeros(10,10,10,10,3);
for episode = 1:num_episodes
% 环境重置
[theta, theta_dot, x, x_dot] = resetEnv();
for step = 1:max_steps
% 状态离散化
s = [discretize(theta, theta_bins),
discretize(theta_dot, theta_dot_bins),
discretize(x, x_bins),
discretize(x_dot, x_dot_bins)];
% ε-贪婪策略选择动作
if rand() < epsilon
a = randi(3); % 随机探索
else
[~,a] = max(Q(s(1),s(2),s(3),s(4),:));
end
% 执行动作,获取新状态和奖励
F = (a-2)*10; % 转换为-10,0,+10
[theta_new, theta_dot_new, x_new, x_dot_new] = ...
simulateStep(theta, theta_dot, x, x_dot, F);
r = getReward(theta_new, theta_dot_new, x_new, x_dot_new);
% Q表更新
s_new = [discretize(theta_new, theta_bins),
discretize(theta_dot_new, theta_dot_bins),
discretize(x_new, x_bins),
discretize(x_dot_new, x_dot_bins)];
Q(s(1),s(2),s(3),s(4),a) = Q(s(1),s(2),s(3),s(4),a) + ...
alpha*(r + gamma*max(Q(s_new(1),s_new(2),s_new(3),s_new(4),:)) - Q(s(1),s(2),s(3),s(4),a));
% 检查终止条件
if abs(theta_new) > pi/6 || abs(x_new) > 2.4
break;
end
% 更新状态
theta = theta_new; theta_dot = theta_dot_new;
x = x_new; x_dot = x_dot_new;
end
% 衰减探索率
epsilon = epsilon * 0.999;
end
matlab复制function [theta_new, theta_dot_new, x_new, x_dot_new] = ...
simulateStep(theta, theta_dot, x, x_dot, F)
dt = 0.02; % 20ms时间步长
m = 0.1; M = 1; l = 0.5; g = 9.81;
b = 0.01; mu = 0.1;
% 计算角加速度
theta_ddot = (m*g*l*sin(theta) - m*l*theta_dot^2*cos(theta) - b*theta_dot) / ...
(m*l^2);
% 计算小车加速度
x_ddot = (F - m*l*theta_ddot*cos(theta) + m*l*theta_dot^2*sin(theta) - mu*x_dot) / ...
(M + m);
% 欧拉积分更新状态
theta_new = theta + theta_dot*dt;
theta_dot_new = theta_dot + theta_ddot*dt;
x_new = x + x_dot*dt;
x_dot_new = x_dot + x_ddot*dt;
end
基础Q-Learning存在样本效率低的问题。我实现了简易经验回放:
matlab复制replay_buffer = cell(10000,1); % 存储最后10000条经验
buffer_ptr = 1;
% 在训练循环中
experience = {s, a, r, s_new};
replay_buffer{buffer_ptr} = experience;
buffer_ptr = mod(buffer_ptr, 10000) + 1;
% 每10步从缓冲区随机采样32条经验进行更新
if mod(step,10) == 0
batch_idx = randperm(min(10000,episode*max_steps),32);
for i = batch_idx
exp = replay_buffer{i};
Q_update(exp{1}, exp{2}, exp{3}, exp{4});
end
end
随着训练进行,逐步降低学习率可提高稳定性:
matlab复制alpha = max(0.01, 0.2*(0.998^episode));
原始状态编码导致维度灾难。改用角度和角速度为主:
matlab复制% 新的离散化设置
theta_bins = linspace(-pi/6, pi/6, 15); % 更精细
theta_dot_bins = linspace(-3, 3, 15);
x_bins = linspace(-2.4, 2.4, 5); % 更粗糙
x_dot_bins = linspace(-1, 1, 5);
经过5000轮训练,系统表现:
matlab复制% 关闭探索
epsilon = 0;
% 测试100次
success_count = 0;
for test = 1:100
[theta, theta_dot, x, x_dot] = resetEnv();
for step = 1:500
s = [discretize(theta, theta_bins), ...];
[~,a] = max(Q(s(1),s(2),s(3),s(4),:));
F = (a-2)*10;
[theta, theta_dot, x, x_dot] = ...
simulateStep(theta, theta_dot, x, x_dot, F);
if abs(theta) > pi/6 || abs(x) > 2.4
break;
end
end
if step == 500
success_count = success_count + 1;
end
end
fprintf('成功率: %.2f%%\n', success_count);
测试结果显示平均成功率达到92%,证明策略有效。
采样时间一致性:实际硬件部署时,确保控制周期严格一致(本项目用20ms)。不稳定的时间间隔会导致性能下降。
传感器噪声处理:仿真中假设完美测量,实际需添加:
matlab复制theta_measured = theta + 0.01*randn(); % 添加高斯噪声
动作延迟补偿:电机响应有延迟,可在状态中包含历史动作:
matlab复制s = [current_state, last_action, action_before_last];
安全限制:
深度Q网络(DQN)扩展:当状态空间更大时,可用神经网络替代Q表:
matlab复制% 简单的DQN结构示例
layers = [
imageInputLayer([4 1 1]) % 输入状态向量
fullyConnectedLayer(64)
reluLayer
fullyConnectedLayer(64)
reluLayer
fullyConnectedLayer(3) % 输出3个动作的Q值
];
优先经验回放:对重要的转移样本(如大奖励或失败)给予更高采样概率。
多步TD学习:使用n步回报而非单步,可加速传播奖励信号。
课程学习:先从简单任务(如较短摆杆)开始训练,逐步增加难度。
这个项目最让我惊讶的是,即使像Q-Learning这样"古老"的算法,经过精心调参和状态设计,也能解决倒立摆这样的非线性控制问题。关键是要理解问题本质——倒立摆控制的核心是角度和角速度,小车位置只要不越界即可,因此状态离散化时应区别对待不同变量。