MATLAB多目标优化:SQP算法实现

序列二次规划(SQP)是一种高效的求解非线性约束优化问题的算法,特别适合解决多目标优化问题。

%% 多目标优化:SQP算法实现
% 功能: 实现序列二次规划(SQP)算法求解多目标优化问题
% 特性: 支持加权和法、ε-约束法、帕累托前沿生成

clear; clc; close all;

%% 1. 多目标优化问题定义
% 目标函数1: Rosenbrock函数 (经典测试函数)
function f1 = rosenbrock(x)
    f1 = (1 - x(1))^2 + 100 * (x(2) - x(1)^2)^2;
end

% 目标函数2: Rastrigin函数 (多峰测试函数)
function f2 = rastrigin(x)
    n = length(x);
    f2 = 10*n;
    for i = 1:n
        f2 = f2 + x(i)^2 - 10*cos(2*pi*x(i));
    end
end

% 目标函数3: Sphere函数 (凸函数)
function f3 = sphere(x)
    f3 = sum(x.^2);
end

% 约束函数 (非线性不等式约束)
function [c, ceq] = constraints(x)
    % 不等式约束: c(x) <= 0
    c = [x(1)^2 + x(2)^2 - 2;    % x1^2 + x2^2 ≤ 2
         x(1) + x(2) - 1.5];      % x1 + x2 ≤ 1.5
    
    % 等式约束: ceq(x) = 0
    ceq = [];
end

% 变量边界
lb = [-2, -2];     % 下界
ub = [2, 2];       % 上界

%% 2. SQP算法核心实现
function [x_opt, fval, exitflag, output] = sqpAlgorithm(objFunc, x0, A, b, Aeq, beq, lb, ub, nonlcon, options)
    % 参数解析
    maxIter = options.MaxIterations;      % 最大迭代次数
    tol = options.Tolerance;              % 收敛容差
    stepSize = options.StepSize;          % 初始步长
    meritParam = options.MeritParam;      % 评价函数参数
    
    x = x0(:);                            % 确保列向量
    n = length(x);                        % 变量维度
    iter = 0;                             % 迭代计数器
    converged = false;                    % 收敛标志
    
    % 初始化历史记录
    history.x = zeros(n, maxIter);
    history.fval = zeros(1, maxIter);
    history.exitflag = 0;
    
    % 主迭代循环
    while iter < maxIter
        iter = iter + 1;
        
        % 1. 计算目标函数和梯度
        [f, grad] = objFunc(x);
        
        % 2. 计算约束值和雅可比矩阵
        if ~isempty(nonlcon)
            [c, ceq] = nonlcon(x);
            [grad_c, grad_ceq] = constraintJacobian(nonlcon, x);
        else
            c = [];
            ceq = [];
            grad_c = [];
            grad_ceq = [];
        end
        
        % 3. 构造QP子问题
        % 目标函数: (1/2)*d'Hd + g'd
        H = eye(n);                  % Hessian矩阵近似 (简化版)
        g = grad;                    % 梯度
        
        % 约束条件: 
        %   Aineq*d <= bineq - Aineq*x
        %   Aeq*d = beq - Aeq*x
        %   c(x) + grad_c*d <= 0
        %   ceq(x) + grad_ceq*d = 0
        
        % 4. 求解QP子问题 (使用quadprog)
        options_qp = optimoptions('quadprog', 'Display', 'off');
        [d, ~, exitflag_qp] = quadprog(H, g, [grad_c; A], [bineq; b] - [c; A*x], ...
                                      [grad_ceq; Aeq], [beq; beq] - [ceq; Aeq*x], ...
                                      [], [], [], options_qp);
        
        if exitflag_qp <= 0
            warning('QP求解失败,尝试减小步长');
            d = zeros(n, 1);
        end
        
        % 5. 线搜索 (使用评价函数)
        alpha = stepSize;
        merit_old = meritFunction(x, f, c, ceq, meritParam);
        
        while true
            x_new = x + alpha * d;
            [f_new, ~] = objFunc(x_new);
            if ~isempty(nonlcon)
                [c_new, ceq_new] = nonlcon(x_new);
            else
                c_new = [];
                ceq_new = [];
            end
            merit_new = meritFunction(x_new, f_new, c_new, ceq_new, meritParam);
            
            if merit_new <= merit_old - 1e-4 * alpha * grad'*d
                break;
            end
            
            alpha = alpha * 0.5;
            if alpha < 1e-6
                alpha = 0;
                break;
            end
        end
        
        % 6. 更新迭代点
        x_prev = x;
        x = x + alpha * d;
        
        % 7. 检查收敛
        change = norm(x - x_prev);
        if change < tol
            converged = true;
            break;
        end
        
        % 保存历史记录
        history.x(:, iter) = x;
        history.fval(iter) = f;
    end
    
    % 输出结果
    x_opt = x;
    fval = objFunc(x);
    exitflag = converged;
    output.iterations = iter;
    output.history = history;
    
    function m = meritFunction(x, f, c, ceq, rho)
        % 评价函数: m(x) = f + ρ*(||c⁺||₁ + ||ceq||₁)
        penalty = 0;
        if ~isempty(c)
            penalty = penalty + sum(max(0, c));
        end
        if ~isempty(ceq)
            penalty = penalty + sum(abs(ceq));
        end
        m = f + rho * penalty;
    end
end

% 约束函数雅可比矩阵计算 (数值差分)
function [grad_c, grad_ceq] = constraintJacobian(conFun, x)
    n = length(x);
    delta = 1e-6;
    [c0, ceq0] = conFun(x);
    
    if isempty(c0) && isempty(ceq0)
        grad_c = [];
        grad_ceq = [];
        return;
    end
    
    % 初始化雅可比矩阵
    if ~isempty(c0)
        m = length(c0);
        grad_c = zeros(m, n);
        for i = 1:n
            x_temp = x;
            x_temp(i) = x_temp(i) + delta;
            [c_temp, ~] = conFun(x_temp);
            grad_c(:, i) = (c_temp - c0) / delta;
        end
    else
        grad_c = [];
    end
    
    if ~isempty(ceq0)
        p = length(ceq0);
        grad_ceq = zeros(p, n);
        for i = 1:n
            x_temp = x;
            x_temp(i) = x_temp(i) + delta;
            [~, ceq_temp] = conFun(x_temp);
            grad_ceq(:, i) = (ceq_temp - ceq0) / delta;
        end
    else
        grad_ceq = [];
    end
end

%% 3. 多目标优化策略
% 加权和法
function [x_opt, fval] = weightedSumMethod(functions, weights, x0, options)
    % 组合目标函数
    combinedFunc = @(x) sum(cellfun(@(f, w) w*f(x), functions, num2cell(weights), 'UniformOutput', false));
    
    % 使用SQP求解
    [x_opt, fval] = sqpAlgorithm(combinedFunc, x0, [], [], [], [], lb, ub, @constraints, options);
end

% ε-约束法
function [x_opt, fval] = epsilonConstraintMethod(functions, primaryIdx, epsilon, x0, options)
    % 主目标函数
    primaryFunc = functions{primaryIdx};
    
    % 其他目标转化为约束
    otherFuncs = functions(setdiff(1:length(functions), primaryIdx));
    epsilonVec = num2cell(epsilon);
    
    % 组合约束函数
    conFunc = @(x) deal([cellfun(@(f) f(x), otherFuncs) - epsilonVec{:}], []);
    
    % 使用SQP求解
    [x_opt, fval] = sqpAlgorithm(primaryFunc, x0, [], [], [], [], lb, ub, conFunc, options);
end

% 帕累托前沿生成
function paretoFront = generateParetoFront(functions, varBounds, numPoints)
    % 使用网格采样生成帕累托前沿
    dim = length(varBounds.lb);
    x1 = linspace(varBounds.lb(1), varBounds.ub(1), numPoints);
    x2 = linspace(varBounds.lb(2), varBounds.ub(2), numPoints);
    
    paretoFront = zeros(numPoints^2, length(functions)+2);
    idx = 1;
    
    for i = 1:numPoints
        for j = 1:numPoints
            x = [x1(i), x2(j)];
            
            % 计算所有目标函数值
            fvals = zeros(1, length(functions));
            for k = 1:length(functions)
                fvals(k) = functions{k}(x);
            end
            
            % 存储结果
            paretoFront(idx, 1:2) = x;
            paretoFront(idx, 3:end) = fvals;
            idx = idx + 1;
        end
    end
end

%% 4. 参数设置与优化执行
% 算法参数
options = struct();
options.MaxIterations = 100;     % 最大迭代次数
options.Tolerance = 1e-6;        % 收敛容差
options.StepSize = 1.0;          % 初始步长
options.MeritParam = 100;        % 评价函数参数

% 初始点
x0 = [0.5, 0.5];

% 目标函数集合
objectiveFunctions = {@rosenbrock, @rastrigin, @sphere};

% 方法1: 加权和法
weights = [0.4, 0.3, 0.3];  % 权重分配
[x_ws, fval_ws] = weightedSumMethod(objectiveFunctions, weights, x0, options);

% 方法2: ε-约束法 (以第一个目标为主)
epsilon = [5, 10];  % 其他目标的约束上限
[x_ec, fval_ec] = epsilonConstraintMethod(objectiveFunctions, 1, epsilon, x0, options);

% 方法3: 帕累托前沿生成
varBounds.lb = lb;
varBounds.ub = ub;
paretoFront = generateParetoFront(objectiveFunctions, varBounds, 20);

%% 5. 结果可视化
% 目标函数曲面可视化
figure('Name', '目标函数曲面', 'Position', [100, 100, 1200, 900]);

% Rosenbrock函数曲面
subplot(2,2,1);
[x1, x2] = meshgrid(linspace(-2, 2, 100), linspace(-1, 3, 100));
Z1 = zeros(size(x1));
for i = 1:size(x1,1)
    for j = 1:size(x1,2)
        Z1(i,j) = rosenbrock([x1(i,j), x2(i,j)]);
    end
end
surf(x1, x2, Z1, 'EdgeColor', 'none');
colormap jet; colorbar;
title('Rosenbrock函数');
xlabel('x_1'); ylabel('x_2'); zlabel('f_1(x)');
view(-30, 30);

% Rastrigin函数曲面
subplot(2,2,2);
Z2 = zeros(size(x1));
for i = 1:size(x1,1)
    for j = 1:size(x1,2)
        Z2(i,j) = rastrigin([x1(i,j), x2(i,j)]);
    end
end
surf(x1, x2, Z2, 'EdgeColor', 'none');
colormap jet; colorbar;
title('Rastrigin函数');
xlabel('x_1'); ylabel('x_2'); zlabel('f_2(x)');
view(-30, 30);

% Sphere函数曲面
subplot(2,2,3);
Z3 = zeros(size(x1));
for i = 1:size(x1,1)
    for j = 1:size(x1,2)
        Z3(i,j) = sphere([x1(i,j), x2(i,j)]);
    end
end
surf(x1, x2, Z3, 'EdgeColor', 'none');
colormap jet; colorbar;
title('Sphere函数');
xlabel('x_1'); ylabel('x_2'); zlabel('f_3(x)');
view(-30, 30);

% 可行域可视化
subplot(2,2,4);
theta = linspace(0, 2*pi, 100);
circle1 = 2*cos(theta); circle2 = 2*sin(theta);
line([0, 1.5], [1.5, 0], 'Color', 'r', 'LineStyle', '--');
fill(circle1, circle2, [0.9, 0.9, 0.9], 'FaceAlpha', 0.3);
hold on;
plot(x_ws(1), x_ws(2), 'bo', 'MarkerSize', 10, 'MarkerFaceColor', 'b');
plot(x_ec(1), x_ec(2), 'rs', 'MarkerSize', 10, 'MarkerFaceColor', 'r');
title('可行域与优化结果');
xlabel('x_1'); ylabel('x_2');
legend('约束边界', 'x_1^2+x_2^2≤2', 'x_1+x_2≤1.5', '加权和法解', 'ε-约束法解');
axis equal; grid on;

% 帕累托前沿可视化
figure('Name', '帕累托前沿', 'Position', [100, 100, 1200, 500]);

% 3D帕累托前沿
subplot(1,2,1);
scatter3(paretoFront(:,3), paretoFront(:,4), paretoFront(:,5), 20, ...
         sqrt(paretoFront(:,3).^2 + paretoFront(:,4).^2 + paretoFront(:,5).^2), 'filled');
xlabel('f_1(x)'); ylabel('f_2(x)'); zlabel('f_3(x)');
title('3D帕累托前沿');
colorbar; colormap jet; grid on;

% 2D投影 (f1 vs f2)
subplot(1,2,2);
scatter(paretoFront(:,3), paretoFront(:,4), 20, paretoFront(:,5), 'filled');
xlabel('f_1(x)'); ylabel('f_2(x)');
title('帕累托前沿 (f_1 vs f_2)');
colorbar; colormap jet; grid on;

% 优化结果比较
figure('Name', '优化结果比较', 'Position', [100, 100, 1000, 600]);

% 目标函数值比较
subplot(2,1,1);
bar([fval_ws(1), fval_ec(1)], 'grouped');
hold on;
bar([fval_ws(2), fval_ec(2)] + 0.2, 'grouped');
bar([fval_ws(3), fval_ec(3)] + 0.4, 'grouped');
set(gca, 'XTickLabel', {'加权和法', 'ε-约束法'});
ylabel('目标函数值');
legend('f_1', 'f_2', 'f_3');
title('不同方法的目标函数值比较');
grid on;

% 收敛过程可视化
subplot(2,1,2);
semilogy(1:output.iterations, output.history.fval(1:output.iterations), 'b-o');
xlabel('迭代次数');
ylabel('目标函数值');
title('SQP算法收敛过程');
grid on;

%% 6. 高级SQP功能扩展
% BFGS Hessian近似更新
function H_new = bfgsUpdate(H, s, y)
    % Broyden-Fletcher-Goldfarb-Shanno (BFGS) 公式
    rho = 1 / (y' * s);
    I = eye(length(s));
    H_new = (I - rho * s * y') * H * (I - rho * y * s') + rho * (s * s');
end

% 信赖域SQP算法
function [x_opt, fval] = trustRegionSQP(objFunc, x0, options)
    % 初始化
    x = x0;
    H = eye(length(x0));  % 初始Hessian近似
    delta = 1.0;           % 信赖域半径
    eta = 0.1;             % 接受阈值
    
    for iter = 1:options.MaxIterations
        % 计算梯度
        [f, grad] = objFunc(x);
        
        % 构造QP子问题 (在信赖域内)
        QP_options = optimoptions('quadprog', 'Display', 'off');
        [d, ~, exitflag] = quadprog(H, grad, [], [], [], [], -delta, delta, [], QP_options);
        
        if exitflag <= 0
            d = zeros(size(x));
        end
        
        % 计算实际改善量
        [f_new, ~] = objFunc(x + d);
        actualReduction = f - f_new;
        
        % 计算预测改善量
        predReduction = -(grad'*d + 0.5*d'*H*d);
        
        % 计算接受比率
        rho_k = actualReduction / predReduction;
        
        % 更新信赖域半径
        if rho_k > 0.75
            delta = 2 * delta;
        elseif rho_k < 0.25
            delta = 0.5 * delta;
        end
        
        % 更新迭代点
        if rho_k > eta
            x = x + d;
        end
        
        % 更新Hessian近似 (BFGS)
        if rho_k > eta
            s = d;
            y = grad_new - grad;
            H = bfgsUpdate(H, s, y);
        end
        
        % 检查收敛
        if norm(d) < options.Tolerance
            break;
        end
    end
    
    x_opt = x;
    fval = objFunc(x);
end

% 多目标SQP优化器
function results = multiObjectiveSQP(functions, method, varargin)
    % 参数解析
    p = inputParser;
    addParameter(p, 'Weights', []);
    addParameter(p, 'Epsilon', []);
    addParameter(p, 'PrimaryIndex', 1);
    addParameter(p, 'x0', rand(2,1));
    addParameter(p, 'Options', []);
    parse(p, varargin{:});
    
    options = p.Results.Options;
    if isempty(options)
        options = struct('MaxIterations', 100, 'Tolerance', 1e-6, 'StepSize', 1.0, 'MeritParam', 100);
    end
    
    switch lower(method)
        case 'weighted-sum'
            if isempty(p.Results.Weights)
                weights = ones(1, length(functions)) / length(functions);
            else
                weights = p.Results.Weights;
            end
            [x_opt, fval] = weightedSumMethod(functions, weights, p.Results.x0, options);
            
        case 'epsilon-constraint'
            if isempty(p.Results.Epsilon)
                epsilon = zeros(1, length(functions)-1);
            else
                epsilon = p.Results.Epsilon;
            end
            [x_opt, fval] = epsilonConstraintMethod(functions, p.Results.PrimaryIndex, epsilon, p.Results.x0, options);
            
        case 'pareto-front'
            varBounds.lb = lb;
            varBounds.ub = ub;
            paretoFront = generateParetoFront(functions, varBounds, 50);
            results.paretoFront = paretoFront;
            return;
            
        otherwise
            error('未知的多目标优化方法');
    end
    
    % 返回结果
    results.x_opt = x_opt;
    results.fval = fval;
    results.method = method;
end

%% 7. 实际应用案例:投资组合优化
% 资产收益率函数
function returns = portfolioReturns(weights, assets)
    % 计算投资组合的期望收益
    returns = weights' * mean(assets, 2);
end

% 资产风险函数 (方差)
function risk = portfolioRisk(weights, covMatrix)
    % 计算投资组合的风险 (方差)
    risk = sqrt(weights' * covMatrix * weights);
end

% 投资组合优化问题
function portfolioOptimization()
    % 资产数据 (历史收益率)
    assets = [0.05, 0.08, 0.03, 0.12;  % 股票A
              0.07, 0.04, 0.06, 0.09;  % 股票B
              0.03, 0.06, 0.08, 0.05;  % 债券C
              0.10, 0.12, 0.04, 0.15]; % 商品D
    
    % 协方差矩阵 (简化版)
    covMatrix = cov(assets');
    
    % 目标函数
    f1 = @(w) -portfolioReturns(w, assets);  % 最大化收益 -> 最小化负收益
    f2 = @(w) portfolioRisk(w, covMatrix);    % 最小化风险
    
    % 约束条件: 权重和为1, 权重非负
    nonlcon = @(w) deal([sum(w) - 1], []);  % 等式约束: sum(w)=1
    lb = zeros(4,1);  % 权重下限
    ub = ones(4,1);   % 权重上限
    
    % 使用ε-约束法求解
    options = struct('MaxIterations', 100, 'Tolerance', 1e-6);
    [x_opt, fval] = epsilonConstraintMethod({f1, f2}, 1, 0.1, rand(4,1), options);
    
    % 显示结果
    fprintf('\n===== 投资组合优化结果 =====\n');
    fprintf('资产权重: \n');
    fprintf('  股票A: %.2f%%\n', x_opt(1)*100);
    fprintf('  股票B: %.2f%%\n', x_opt(2)*100);
    fprintf('  债券C: %.2f%%\n', x_opt(3)*100);
    fprintf('  商品D: %.2f%%\n', x_opt(4)*100);
    fprintf('期望收益: %.2f%%\n', -fval(1)*100);
    fprintf('风险(标准差): %.2f%%\n', fval(2)*100);
    
    % 可视化有效前沿
    figure;
    weights = rand(100, 4);
    weights = weights ./ sum(weights, 2);  % 归一化为权重和=1
    
    returns = arrayfun(@(i) portfolioReturns(weights(i,:)', assets), 1:size(weights,1));
    risks = arrayfun(@(i) portfolioRisk(weights(i,:)', covMatrix), 1:size(weights,1));
    
    scatter(risks*100, -returns*100, 30, 'filled');
    hold on;
    scatter(fval(2)*100, -fval(1)*100, 100, 'ro', 'filled');
    xlabel('风险(%)');
    ylabel('期望收益(%)');
    title('投资组合有效前沿');
    grid on;
    legend('随机投资组合', '优化组合', 'Location', 'best');
end

% 运行投资组合优化案例
portfolioOptimization();

SQP算法原理与实现详解

1. SQP算法基本原理

序列二次规划(SQP)是一种求解非线性约束优化问题的高效算法,其核心思想是将原问题转化为一系列二次规划(QP)子问题求解:

算法流程:

  1. 在当前迭代点xk处,构造拉格朗日函数: \(L(x,λ)=f(x)+∑_iλ_ic_i(x)+∑_jλ_jceq_j(x)\)
  2. 求解QP子问题:
  3. 更新迭代点:\(x_{k+1}=x_k+α_kd_k\)
  4. 检查收敛条件,若不收敛则返回步骤1

2. 多目标处理策略

(1) 加权和法

将多目标问题转化为单目标问题:

\(min_x∑_{i=1}^mw_if_i(x)\)

其中\(w_i\)为权重,满足\(∑w_i=1\),\(w_i≥0\)

优点: 简单易实现

缺点: 权重选择敏感,难以获得非凸帕累托前沿

(2) ε-约束法

选择一个主目标,其余目标转化为约束:

\(min_xf_p(x)\)

\(s.t. f_i(x)≤ε_i,i\neq{p}\)

\(c(x)≤0,ceq(x)=0\)

优点: 能获取特定目标的优化解

缺点: 需要预先设定ε值

(3) 帕累托前沿生成

通过采样或优化方法生成一组帕累托最优解:

  1. 网格采样:在可行域内均匀采样
  2. 优化采样:从多个初始点运行单目标优化
  3. 进化算法:使用NSGA-II等多目标进化算法

3. SQP算法核心组件实现

(1) 约束雅可比矩阵计算

function [grad_c, grad_ceq] = constraintJacobian(conFun, x)
    n = length(x);
    delta = 1e-6;
    [c0, ceq0] = conFun(x);
    
    % 数值差分计算梯度
    if ~isempty(c0)
        m = length(c0);
        grad_c = zeros(m, n);
        for i = 1:n
            x_temp = x; x_temp(i) += delta;
            [c_temp, ~] = conFun(x_temp);
            grad_c(:, i) = (c_temp - c0)/delta;
        end
    end
    
    if ~isempty(ceq0)
        p = length(ceq0);
        grad_ceq = zeros(p, n);
        for i = 1:n
            x_temp = x; x_temp(i) += delta;
            [~, ceq_temp] = conFun(x_temp);
            grad_ceq(:, i) = (ceq_temp - ceq0)/delta;
        end
    end
end

(2) 评价函数设计

function m = meritFunction(x, f, c, ceq, rho)
    % 评价函数: m(x) = f + ρ*(||c⁺||₁ + ||ceq||₁)
    penalty = 0;
    if ~isempty(c)
        penalty = penalty + sum(max(0, c)); % 不等式约束惩罚
    end
    if ~isempty(ceq)
        penalty = penalty + sum(abs(ceq));   % 等式约束惩罚
    end
    m = f + rho * penalty;
end

(3) BFGS Hessian近似更新

function H_new = bfgsUpdate(H, s, y)
    % Broyden-Fletcher-Goldfarb-Shanno (BFGS) 公式
    rho = 1 / (y' * s);
    I = eye(length(s));
    H_new = (I - rho * s * y') * H * (I - rho * y * s') + rho * (s * s');
end

4. 算法参数选择与调优

参数 物理意义 推荐值 影响
MaxIterations 最大迭代次数 50-500 影响计算时间与收敛性
Tolerance 收敛容差 1e-4~1e-8 影响解的精度
StepSize 初始步长 0.5~1.0 影响收敛速度
MeritParam 评价函数参数 10~1000 平衡目标函数与约束违反
TrustRegionRadius 信赖域半径 0.1~10.0 控制QP子问题规模

调参指南:

  1. 对于简单问题:增大步长(1.0),减小容差(1e-6)
  2. 对于病态问题:减小步长(0.5),增大评价函数参数(1000)
  3. 对于大规模问题:使用信赖域版本,BFGS更新Hessian

参考代码 MATLAB,多目标优化,使用SQP算法 www.youwenfan.com/contentcnm/83548.html

5. 性能评估与可视化

(1) 收敛性分析

% 绘制目标函数值随迭代变化
semilogy(1:iter, history.fval(1:iter), 'b-o');
xlabel('迭代次数');
ylabel('目标函数值(log)');
title('SQP算法收敛过程');
grid on;

(2) 帕累托前沿可视化

% 3D帕累托前沿
scatter3(f1_vals, f2_vals, f3_vals, 20, sqrt(f1_vals.^2+f2_vals.^2+f3_vals.^2), 'filled');
xlabel('f_1'); ylabel('f_2'); zlabel('f_3');
colorbar; colormap jet;

(3) 有效前沿分析

% 投资组合有效前沿
scatter(risks, returns, 30, 'filled');
hold on;
plot(efficient_risks, efficient_returns, 'r-', 'LineWidth', 2);
xlabel('风险'); ylabel('收益');
title('投资组合有效前沿');
posted @ 2025-12-05 10:22  徐中翼  阅读(3)  评论(0)    收藏  举报