【强化学习】策略迭代如何解决01背包问题?内附代码

背景

看Sutton的Reinforcement learning: An introduction,里面将策略迭代作为一种基于动态规划的方法。

书中举了个grid world的例子,非常符合书中的数学原理,有状态转移概率,每个时间步就是每个state等.....

动态规划作为一个常见的面试八股,经常出现于笔试题中,一般都是利用价值-动作表来去解决。

网上查了查,貌似目前全网所有的策略迭代的例子都是grid world这个例子,虽然很清晰,但是受限于这个问题框架,不舒服。

那么有意思的问题来了:如何使用策略迭代(Policy Iteration)解决01背包(knapsack 01)问题?

一、策略迭代

1、策略评估

给定策略\(\pi\),计算其价值函数,即为策略评估,有时也称其为预测问题。

根据贝尔曼方程:\(v_{\pi}(s) = \sum_{a}{\pi(a \mid s) \sum_{s', r}{P(s',r \mid s,a)(r + \gamma v_{\pi}(s'))}}\) 不断迭代,直至收敛,具体的伪代码算法如下:

2、策略改进

求解最优的策略和价值函数,即为策略改进,有时也称其为控制问题。

策略改进定理:如果对于任意 \(s \in S\)\(q_{\pi}(s, \pi'(s)) \geq v_{\pi}(s)\),那么策略 \(\pi'\) 不会比策略 \(\pi\) 差(一样好或者更好)。

构造一个贪心策略来满足策略改进定理:
\( q_{\pi}(s, a) = \sum_{s', r} P(s', r \mid s, a) \left[ r + \gamma v_{\pi}(s') \right] \)
\( \pi'(s) = \arg\max_{a} q_{\pi}(s, a) \)
可知通过执行这种贪心算法,我们就可以得到一个更好的策略。

3、策略迭代算法流程

二、策略迭代代码解决01背包问题

根据我调试的经验,解决01背包问题要比解决完全背包问题更加麻烦一点。

问题在于:01背包问题中每一个物品只能拿取一次,而完全背包可以多次重复拿取。
GitHub仓库中是代码,因为顺手打开的matlab就用matlab实现了,改一下语法就是python。

https://github.com/Tylerr77/Policy-Iteration-Solving-01-Knapsack-Problem?tab=GPL-3.0-1-ov-file

% 0/1 背包问题的策略迭代
clc;
clear;
%% 输入
w = [2, 3, 4, 5, 6, 7, 8]; % 物品的重量
r = [3, 1, 5, 6, 7, 8, 9]; % 物品的价值,奖励

max_W = 9; % 背包容量

n = length(w); % 物品数量

P = 1; % 因为选取下一个物品是固定的,所以状态转换的概率是1

gama = 1; % 衰减因子

% 初始化策略
policies = randi([0, n],1, max_W + 1) % 策略数组,数值表示某状态时放入物品的index,0表示不选择

% 初始化价值函数
% 代表V(s),也就是V的index代表了每个状态下所能得到的最大价值,
% V(s)表示 背包容量为s - 1 状态时的价值函数(matlab索引从1开始)
V = zeros(1, max_W + 1)  % 背包容量从 0 到 W,每个容量的最大价值初始化为 0

% 策略迭代过程
while true
    % 步骤 1:策略评估
    % 使用当前策略计算每个状态的价值函数
    disp('策略评估');
    V_new = V; % 用于存储更新后的价值函数

    % 根据贝尔曼方程计算所有状态s的状态值函数
    % 迭代的方式进行策略评估,直到价值函数收敛
    while true
        f = 0;
        for s = 2:(max_W + 1)
            % ------------不同环境这段代码不同-------------- %
            % 评估当前策略下,s状态下的V(s)
            if policies(s) ~= 0 && w(policies(s)) <= s - 1
                V_new(s) = P * (r(policies(s)) + gama * V(s - w(policies(s))));
            else
                V_new(s) = V_new(s - 1);
            end
            % --------------------------------------------- %

            f = max(f, abs(V_new(s) - V(s)));
        end

        if f < 1e-6
            break;
        else
            V = V_new;
        end

    end

    % 步骤 2:策略改进
    % 根据当前值函数 V,选择每个状态下的最优动作
    disp('策略改进');
    policy_new = policies;
    
    policy_new(1) = 0; % s = 1背包没有空间,策略一定是不选择

    value_q = zeros(max_W + 1, n + 1); % q(s, a) 状态-动作对价值函数
    for s = 2:(max_W + 1)
        for a = 2:n + 1 % a = 1代表什么都不放,= 2放1号物品,= 3放2号物品....
            
            % ------------不同环境这段代码不同-------------- %
            % 01背包,物品仅仅可以被选中一次,但是什么都不放可以多次选中
            % 计算动作价值函数
            store = s; flag = 0;
            while store - w(a - 1) >= 1
                if policy_new(store - w(a - 1)) == a - 1
                    flag = 1;
                    break;
                end
                store = store - w(a - 1);
            end

            if flag ~= 1 && w(a - 1) <= s - 1
                value_q(s, a) = r(a - 1) + V(s - w(a - 1));
            else % 什么都不放,或者放不进去,则继承上一个状态的价值,价值为value_q(s - 1)
                value_q(s, a) = value_q(s, a - 1);
            end

            % --------------------------------------------- %

        end
        [max_q, max_act] = max(value_q(s,:));
        policy_new(s) = max_act - 1;
    end

    % 如果策略没有变化,表示已经收敛
    if all(policy_new == policies)
        disp('策略已收敛');
        break;
    else
        % 更新策略
        policies = policy_new;
    end

end


% 输出最终策略和最大价值
disp('最优策略:');
disp(policies); % 输出最优策略 pi(s, a)
disp('最大价值:');
disp(V(max_W + 1)); % 输出最大价值,背包容量为 W 时的最大值
weight = max_W + 1;
while weight ~= 0 && policies(weight) ~= 0
    fprintf('物品 %d: 重量 = %d, 价值 = %d\n', ...
        policies(weight), w(policies(weight)), r(policies(weight)));
    weight = weight - w(policies(weight));
end



posted @ 2024-11-20 02:28  Tyler77  阅读(249)  评论(0)    收藏  举报