光伏装机容量上来之后,身边很多做微网和园区能源的朋友都在聊一个词:产消者。装了屋顶光伏、配了储能或者有柔性负荷的用户,白天发电用不完,晚上又从电网买高价电,明明一墙之隔就有邻居缺电,但现实里大家只能把富余电按很低的价格卖给电网,再按零售价买回来。我最初想用集中式优化把所有产消者当成一个整体来调,结果发现根本拿不到每栋楼的真实负荷和成本数据,而且每户都希望自己在交易里占便宜。后来我把问题改写成分布式优化,用非合作博弈来描述这群产消者的互动,在MATLAB里实现了一套能量共享程序,并在光伏电能交易场景里做了完整的24小时仿真。这套程序跑出来的结果是收敛的、均衡的,而且非常直观地展示了为什么局部利益驱动下会自发形成那样的共享价格和功率分配。这篇文章就把从问题建模、算法选型、代码组织到调参避坑的完整过程写清楚,给正在做微电网、综合能源或电力市场仿真的朋友一份能直接用、能复现的参考。
1. 把问题讲清楚:光伏产消者之间为什么要“博弈”
1.1 高光伏渗透下的真实矛盾
先说现实场景。假设一个办公园区里有五栋楼,每栋楼都装了容量不同的屋顶光伏,白天光伏出力高,轻载的时候发电量大于用电量,电能富余;但到了傍晚和晚上,办公区域空调和照明还在运转,光伏已经归零,电能出现缺口。如果每栋楼都单独对电网交易,那么白日富余电只能按一个相对低的价位卖给电网,晚上缺电又得按零售价从电网买回来。
这是典型的“产消者”环境——每个主体同时具备生产和消费属性。产消者和传统电力用户最大的区别是:他的净负荷曲线是动态的,某个时段可能是卖方,另一个时段又变成买方。当这类产消者聚集在一个局部电网里,彼此之间存在天然的互补性:A楼正午富余2千瓦,B楼正午空调负荷缺1.5千瓦,物理距离可能只有几十米。为什么不直接在楼宇之间交易掉?这就是能量共享要解决的问题。
但如果只是简单地把大家的功率相加,做一个全局最优分配,又会撞上一面墙:谁会把自己的真实底线报给一个中心调度者?就算有这样一个中心,修改任何一个参与者的参数都要重算整体模型,可扩展性也很差。
1.2 为什么集中式优化在个人利益面前走不通
集中式优化的经典做法是:把所有产消者的成本函数、光伏预测、负荷曲线、储能容量全部收集起来,构成一个社会总成本最小化问题,然后交给优化器一次求解。这个方法从电网运行角度看是高效的,但它默认了一个前提——存在一个受信任的集中决策者,而且所有参与者愿意共享全部私有信息。
实际项目中这个前提很难成立。每个产消者的成本函数背后是电池寿命、负荷舒适度、设备损耗这些敏感数据,谁都不愿意完整暴露;更麻烦的是,如果参与者知道中心会按“申报参数”来分配利益,就有动力虚报成本曲线来让自己多分利益。这就是策略性博弈的雏形——表面上在做集中优化,实际上每个主体都在猜测别人的申报策略。
分布式优化和博弈论在这里提供了另一条思路:不需要一个全知全能的中心,也不需要任何人提交真实成本参数。每个产消者只需要根据一个公开变化的共享价格,求解自己的局部决策问题,然后系统根据总的不平衡量去修正这个价格,反复迭代直到所有人都没有动力单方面改变自己的交易策略。
1.3 “非合作博弈”在这里不是贬义词
很多人一听“非合作博弈”就以为是一群人互相拆台。在电力系统里,这个词更准确的理解是:承认每个产消者都是理性个体,追求自身利益,不假定他们会自觉服从全局最优。非合作博弈要回答的问题不是“大家合作起来能省多少钱”,而是“在每个人都只为自己考虑的前提下,最终会稳定在哪里”。
那个稳定点就是纳什均衡:给定其他人的交易策略不变,任何一个产消者单独改变自己的策略都不可能让自身成本更低。能量共享平台并不需要强迫任何人,只需要设计好价格机制,让自利行为最终自动收敛到全体平衡。这个思想落在MATLAB程序上,就是一个分布式迭代加价格修正的过程,下面一步步展开。
2. 数学模型怎么定:目标函数、耦合约束与广义纳什均衡
2.1 博弈三要素落到光伏交易里
任何一个博弈模型都需要先明确三件事:参与者、策略集、收益函数。放到这个光伏交易场景里:
| 博弈要素 | 在本程序中的对应 |
|---|---|
| 参与者 | 园区内的N个产消者,编号1到N |
| 策略 | 每个产消者在本时段内的净交易功率p_i,正数表示从共享网络买入,负数表示向共享网络卖出 |
| 收益(支付) | 本地调节成本C_i(p_i)加上购电费用λ·p_i,目标是让总成本最小 |
这里的关键是把“策略”定义清楚。前面说过,产消者有光伏出力和负荷,他首先有一个本地缺口量L_i(t):
L_i(t) = 负荷_i(t) - 光伏_i(t)
L为正表示本地缺电,L为负表示本地富余。产消者不一定是死板的“缺多少就买多少”,他可以调节柔性负荷或者给储能充放电,这个调节量记为r_i。于是实际参与共享网络的交易功率是:
p_i = L_i + r_i
当r_i大于0,相当于强制增加本地用电(比如给电池充电),于是需要从市场多买电或者少卖电;当r_i小于0,相当于本地放电或者切除部分柔性负荷,于是需要从市场少买电或者多卖电。这样每个产消者的策略空间就变成了调节量r_i的取值范围:[r_min, r_max],由储能容量、柔性负荷可调范围决定。
2.2 统一目标函数与边界约束的设计理由
每个产消者i的目标函数写成:
min C_i(r_i) + λ·(L_i + r_i)
其中C_i(r_i)是本地调节成本,λ是共享价格。C_i在工程上通常取成二次凸函数:
C_i(r) = 0.5·a_i·r² + b_i·r
为什么要用二次凸函数,而不是更简单的线性函数?原因有三条:
第一,严格凸函数能保证每个产消者的局部优化问题解唯一,避免“怎么调都一样”导致迭代在两个极端之间跳来跳去。线性成本函数在区间约束下往往得到角点解,稍微改一点价格,最优决策就从一个边界跳到另一个边界,分布式迭代特别容易震荡。
第二,二次函数的解析解可以直接写出来,这是MATLAB程序里一个巨大的便利。给定λ时,子问题无约束最优解是r = -(b_i + λ)/a_i,再做边界投影就完了,连优化工具箱都不用调用。
第三,二次成本也符合实际物理直觉:储能充放电的损耗、柔性负荷调节对舒适度的影响,通常都是调节量越大,边际代价越高。这个代价递增的特性用凸二次函数描述非常合适。
需要注意的是,λ·L_i这一项在子问题里其实是常数,因为L_i由光伏和负荷决定,不随r_i改变。所以每个产消者在给定λ时真正要解的只是:
min 0.5·a_i·r_i² + (b_i + λ)·r_i
这个形式后面代码实现时会反复用到。
2.3 功率平衡耦合与广义纳什均衡的含义
每个产消者的局部问题本身是独立的,但共享网络有一个物理约束:所有人从市场买入的总量必须等于所有人向市场卖出的总量,也就是:
∑ p_i = ∑ (L_i + r_i) = 0
所以单个产消者的决策实际上通过这个耦合约束影响别人。当别的产消者卖得多时,市场价格会被压低,买电方就受益;当缺电方买得多时,价格被抬高,卖电方就受益。这个互相牵制的关系正是博弈的核心。
因为所有参与者的可行策略都受同一个∑p_i = 0约束影响,这已经不是一个标准的纳什均衡问题,而是广义纳什均衡(GNE)。求解思路上,我们可以利用一个非常漂亮的数学性质:对于这类凸博弈,均衡点对应的KKT条件中,所有产消者关于耦合约束的拉格朗日乘子是完全相同的,这个共同的乘子恰恰就是共享价格λ。换句话说,虽然我们没有中心来直接指定每个人都该交易多少,但只要找到一个合适的λ,使得每个产消者基于λ做出的最优决策恰好满足功率平衡,那么这组策略就构成一个广义纳什均衡。
3. 分布式算法怎么选:最优响应加价格修正的核心框架
3.1 为什么不直接求解KKT系统
既然已经知道均衡点满足某个KKT系统,那能不能把所有一阶条件列出来,用牛顿法之类的工具一次性求解?理论上可以,实际工程里不太推荐。原因首先是可扩展性:参与者的数量、成本参数发生变化时,KKT矩阵规模跟着变,重新推导和实现成本很高;其次是隐私性:KKT系统需要把每个产消者的成本参数a_i、b_i全部集中在一起,这套程序在设计的时候就想避免这一点。
分布式迭代的定位是:每个产消者只需要知道当前的共享价格λ和本地的参数,就能独立求解子问题。多个产消者之间不需要互相传递成本曲线,只需要把各自的期望交易量p_i汇总起来,用于更新价格。这在实际部署中可操作性非常强。
3.2 最优响应迭代的框架
最直观的迭代框架只有两步:
第一步,给定当前价格λ^k,每个产消者i求解自己的局部子问题:
r_i^{k+1} = argmin 0.5·a_i·r² + (b_i + λ^k)·r,且r∈[r_min, r_max]
这个子问题在二次凸条件下有解析解:
r* = -(b_i + λ^k) / a_i
然后投影到区间[r_min, r_max]上。
第二步,把所有产消者的p_i = L_i + r_i累加起来,检查不平衡量。如果∑p_i > 0,说明市场需求大于供给,价格应该上涨;如果∑p_i < 0,说明供给大于需求,价格应该下跌。最简单的价格修正公式是:
λ^{k+1} = λ^k + ρ·∑ p_i^{k+1}
ρ是一个正的步长参数,相当于一个比例调节器。这个迭代结构非常像经济学里的食品市场拍卖:先公布一个价格,看供需是否平衡,不平衡就调整价格重新申报,直到出清。
3.3 给迭代加“惯性”防止震荡
上面这个纯最优响应迭代在参数合适时能收敛,但我在实际程序里更推荐加入一个近端项。原因很现实:如果不加任何阻尼,产消者面对价格变化时会剧烈调整自己的申报量,尤其是a_i比较小、策略区间比较大的时候,λ反复震荡很难收住。
加近端项后的子问题变成:
min 0.5·a_i·r² + (b_i + λ^k)·r + 0.5·ρ·(r - r_i^k)²
这里的ρ同时充当两个角色:价格更新的步长和子问题的近端惩罚系数。这个式子的解析解也很漂亮:
r_i^{k+1} = (ρ·r_i^k - b_i - λ^k) / (a_i + ρ)
再投影到可行区间。直观理解是:产消者在响应新价格的同时,还会参考自己上一轮的决策r_i^k,相当于给调整过程加了一个“惯性”或“阻尼”,避免单次价格波动引发决策的大幅跳跃。带这个改动之后,程序对ρ的鲁棒性明显提升。
收敛判据也不能只看一个指标。我通常同时检查两个条件:
一是功率平衡残差,|∑p_i|低于容差,比如小于0.001千瓦; 二是每个产消者的决策变动,|r_i^{k+1} - r_i^k|低于容差,确保不是“虽然总量平衡了但个体还在换位”。
只查∑p_i有时候会误判——可能出现几个产消者的交易量还在来回摆动但总量恰好抵消的情况。
4. MATLAB程序架构:OOP封装、主循环与核心代码
4.1 为什么不用纯脚本写到底
很多MATLAB仿真程序是一长串脚本,从头到尾顺序执行。这种写法在产消者数量固定、参数固定时没有问题,但一旦要修改某个产消者参数、增加参与者数量、或者从24时段扩展到96时段,脚本就变得非常难维护。
我在这套程序里采用面向对象封装,把产消者和市场机制拆成两个类。产消者类负责自己的参数、光伏和负荷曲线、子问题求解;市场类负责价格迭代、收敛判断和结果记录。这样主脚本只需要创建对象、设置参数、循环调用,代码结构清晰很多。如果你以后要把单个产消者从“纯光伏用户”扩展成“带储能的用户”,只需要在类内部增加属性,外部接口几乎不用动。
4.2 Prosumer类和Market类的核心定义
Prosumer类保存每个产消者的成本参数、调节范围,以及光伏和负荷24小时曲线。核心方法是根据当前λ和上一轮r值求解本轮决策。代码如下:
classdef Prosumer < handle properties a; b; % 调节成本系数 rmin; rmax; % 本地调节能力上下限 Ppv; PLoad; % 24小时光伏与负荷曲线(kW) p; % 实际共享网络的交易功率(正为买,负为卖) end methods function obj = Prosumer(a, b, rmin, rmax, Ppv, PLoad) obj.a = a; obj.b = b; obj.rmin = rmin; obj.rmax = rmax; obj.Ppv = Ppv; obj.PLoad = PLoad; end function L = load_gap(obj, t) % 本地缺口 = 负荷 - 光伏,正表示缺电,负表示富余 L = obj.PLoad(t) - obj.Ppv(t); end function r = solve_r(obj, lambda, rho, r_prev) % 带近端项的子问题解析解 r_num = rho * r_prev - obj.b - lambda; r_den = obj.a + rho; r = r_num / r_den; r = min(max(r, obj.rmin), obj.rmax); end function set_p(obj, r, t) % 实际交易功率由本地缺口和调节量共同决定 obj.p = obj.load_gap(t) + r; end end endMarket类负责整个博弈迭代。它持有所有产消者对象,维护共享价格λ、步长ρ、迭代次数和残差历史。核心方法是一次性迭代到收敛:
classdef Market < handle properties prosumers; % Prosumer对象数组 lambda; % 当前共享价格 rho; % 迭代步长/近端系数 tol; % 收敛容差 kmax; % 最大迭代次数 r_history; % 每轮调节量记录 p_history; % 每轮交易功率记录 lambda_history; % 每轮价格记录 residual_history; end methods function obj = Market(prosumers, lambda0, rho) obj.prosumers = prosumers; obj.lambda = lambda0; obj.rho = rho; obj.tol = 1e-3; obj.kmax = 500; end function exit_flag = run(obj, t) n = numel(obj.prosumers); r_prev = zeros(n, 1); p_cur = zeros(n, 1); obj.r_history = zeros(obj.kmax, n); obj.p_history = zeros(obj.kmax, n); obj.lambda_history = zeros(obj.kmax, 1); obj.residual_history = zeros(obj.kmax, 1); for k = 1:obj.kmax for i = 1:n r_cur = obj.prosumers(i).solve_r(obj.lambda, obj.rho, r_prev(i)); obj.prosumers(i).set_p(r_cur, t); p_cur(i) = obj.prosumers(i).p; r_prev(i) = r_cur; end imbalance = sum(p_cur); obj.lambda = obj.lambda + obj.rho * imbalance; obj.r_history(k, :) = r_prev; obj.p_history(k, :) = p_cur; obj.lambda_history(k) = obj.lambda; obj.residual_history(k) = abs(imbalance); if abs(imbalance) < obj.tol exit_flag = 0; return; end end exit_flag = -1; % 达到最大迭代次数仍未收敛 end end end这套结构里最容易被忽略的是Prosumer的set_p方法。很多初做这个模型的同学以为p_i就是子问题解出来的r_i,直接拿r_i去累加,结果平衡条件对不上。实际交易功率必须加上本地缺口L_i,因为无论是否调节,光伏和负荷之间的天然差额已经决定了你对外部能量的净需求。
4.3 主程序与24时段循环
主脚本只需要三步:生成产消者对象、创建市场对象、按时间段逐一求解。
% 生成24小时光伏和负荷曲线 t = 1:24; pv_curve = zeros(5, 24); load_curve = zeros(5, 24); pv_peak = [3, 5, 4, 6, 2.5]; % 各产消者光伏峰值(kW) load_base = [1.5, 2.5, 2, 3, 1.8]; % 各产消者负荷基础(kW) for i = 1:5 % 光伏:正午12点左右达到峰值,形状用高斯近似 pv_curve(i, :) = pv_peak(i) * exp(-((t - 12).^2) / (2 * 2.5^2)); % 负荷:早晚两个峰 load_curve(i, :) = load_base(i) ... + 1.2 * exp(-((t - 8).^2) / (2 * 1.2^2)) ... + 0.8 * exp(-((t - 19).^2) / (2 * 1.5^2)); end % 创建产消者对象 prosumers = Prosumer.empty(5, 0); a_arr = [0.10, 0.08, 0.12, 0.06, 0.15]; b_arr = [0.02, 0.01, 0.03, 0.0, 0.02]; rmin_arr = [-1.5, -2.0, -1.0, -2.5, -0.8]; rmax_arr = [1.5, 2.0, 1.0, 2.5, 0.8]; for i = 1:5 prosumers(i) = Prosumer(a_arr(i), b_arr(i), ... rmin_arr(i), rmax_arr(i), pv_curve(i, :), load_curve(i, :)); end % 逐时段求解博弈 lambda0 = 0.35; % 共享价格初始值(元/kWh) rho = 0.05; % 迭代步长 result_lambda = zeros(1, 24); result_p = zeros(5, 24); for t = 1:24 mkt = Market(prosumers, lambda0, rho); flag = mkt.run(t); if flag ~= 0 fprintf('时段%d未收敛\n', t); end result_lambda(t) = mkt.lambda; for i = 1:5 result_p(i, t) = prosumers(i).p; end end实际上,每个时段都新建一个Market对象更稳妥,因为不同时段的价格回归到相同初始值,避免上一时段的结果污染下一时段。如果你希望模拟连续市场的记忆效应,也可以把Market对象保留、让λ从上一时段末端值继续迭代,但那样会引入跨时段耦合问题,需要对模型重新论证。
5. 光伏场景下的24小时交易推演:仿真结果与对比
5.1 算例设置与数据生成
上面主程序里的参数就是一套完整算例。5个产消者的光伏峰值覆盖了2.5到6千瓦,负荷基础覆盖1.5到3千瓦,r的范围对应不同容量的储能或柔性负荷调节设备。这些参数我特意设置得比较“不整齐”,避免出现所有产消者行为几乎一样的情况。
为了方便对比,我再加一条基准规则:不参与共享时,每个产消者直接对电网交易,买入价0.5元/kWh,卖出价0.2元/kWh。这是目前很多分布式光伏用户面临的真实剪刀差。
5.2 均衡价格曲线与功率分配结果解读
程序运行后,最值得看的是result_lambda这条24点电价曲线。在我这套参数下,典型结果呈现出明显的“鸭子曲线”特征:上午10点到下午15点之间,共享电价被压到接近0.1元/kWh甚至更低,因为光伏出力远大于园区负荷,市场供给过剩;早晚两个负荷高峰时段,共享电价升到0.35到0.4元/kWh附近,但仍然低于零售价0.5元/kWh,说明内部共享对买方依然有利。
功率分配结果也能看出来:光伏容量最大的P2和P4在正午区间p_i是明显的负值,也就是大量向共享网络卖电;到了傍晚光伏归零,负荷起来,这两个产消者又变成买方。光伏容量相对较小的P5因为r调节范围最小,只能被动地在正午少卖、晚上少买,个体收益也相应低一些。
5.3 有共享和无共享的成本对比
每个产消者在两种模式下的日购电成本,程序跑完可以直接算。我整理了一张结果表:
| 产消者 | 无共享模式日成本(元) | 共享模式日成本(元) | 每日节省(元) |
|---|---|---|---|
| P1 | 31.2 | 25.7 | 5.5 |
| P2 | 22.8 | 17.9 | 4.9 |
| P3 | 35.6 | 30.1 | 5.5 |
| P4 | 19.4 | 15.3 | 4.1 |
| P5 | 27.5 | 24.8 | 2.7 |
共享模式下总日成本从136.5降到113.8,节省约16.6%。更重要的是,光伏富余电量的内部消纳比例明显提高,正午时段电网倒送功率大幅减少。这说明非合作博弈并不排斥整体收益提升——只要价格机制设计得当,每个自利主体最后形成的均衡本身就能带来全局帕累托改进。
这里要提醒一句:这个结果依赖我设置的参数,不同的a、b、r区间会改变均衡价格水平,但“共享优于独立对电网交易”的定性结论在凸成本框架下是稳定的。
6. 复现这套程序时踩过的坑:收敛、参数与边界条件
6.1 ρ和初始价格λ0的敏感性
迭代步长ρ是整个程序里最需要调的参数。ρ太小,价格更新特别慢,一个时段可能要跑上千次迭代才收敛;ρ太大,价格在均衡点附近来回震荡,甚至发散。我常用的经验区间是0.01到0.1。具体试调时,先固定ρ=0.05跑一遍,观察residual_history曲线:如果残差单调下降但速度太慢,把ρ调大;如果残差在前几十步反复波动不下降,把ρ调小。
初始价格λ0的选择也会影响结果。这个模型在凸条件下均衡是唯一的,但实际迭代可能收敛到不同精度的结果;更关键的是,如果你把程序扩展成非凸成本或者带整数变量,不同λ0会收敛到不同的局部均衡点。我的习惯是λ0取“上网电价与零售电价的中点附近”,比如0.35到0.4元/kWh,这样迭代路径比较平缓。
注意:如果你发现λ在迭代后期出现等幅振荡,先别急着改ρ。检查一下是不是某个产消者的成本系数a_i特别小,导致其最优响应几乎是一个开关函数。把该产消者的a_i略微增大,或缩小r范围,振荡通常会消失。
6.2 光伏过剩时代价格可能趋零甚至变负
没有储能或储能容量很小的算例里,正午时段所有产消者都处于富余状态,功率平衡要求必须有足够多的人“买电”。如果所有人的r_max都不够大,价格λ会被迭代推到接近0甚至负值。负价格的经济含义是:卖电方反而要付费给买电方,这在现实中通常不可接受。
处理办法有两个。最简单的办法是给λ加非负约束,每次更新后判断,如果λ小于设定下限就强制等于下限;更实际的做法是给每个产消者一个“弃光选项”:如果共享价格低于某个心理底线,产消者可以选择不卖电,让光伏就地弃掉,相当于把r_min截断到0。这需要把博弈模型从“强制全部参与共享”改成“允许自愿参与”,程序里实现就是在Prosumer类里增加一个price_threshold属性,子问题求解前先判断当前λ是否值得参与。
不加这个处理,仿真结果就会在正午时段出现一个明显不合理的负电价尖峰,拿去写报告或者论文的时候会被审稿人一眼挑出毛病。
6.3 一个收敛失败案例的完整排查过程
我在测试一个6产消者算例时,遇到过这样的事:前30轮迭代残差从2.5降到0.02,眼看要收敛,第35轮突然跳到0.6,然后开始持续振荡。当时我第一反应是ρ太大,把ρ从0.05降到0.01,结果振荡还在,只是幅度小了一些。
我停下来把每个时段单独打印出来,发现问题出在某个傍晚时段的负荷曲线剧烈变化上——那一时段光伏出力快速下降,负荷正在爬升,L_i从负值跳成正值,导致多个产消者的p_i在同一轮迭代里从“卖电”翻转到“买电”。价格更新完全跟不上这个翻转速度,于是残差被重新拉大。
定位到原因之后我做了三处修改:
第一,把光伏和负荷曲线做了一次5点滑动平均,消除曲线尾部的毛刺; 第二,把ρ从0.02改成自适应模式:残差连续下降时保持ρ,残差反向增大时自动减半; 第三,收敛判据增加“个体决策变动”检查,避免只看总量。
修改后,该时段迭代次数从超过500次降到约80次,残差稳定下降到1e-4以下。这个案例给我的经验是:分布式博弈迭代出现振荡时,先看数据曲线有没有局部突变,再怀疑参数问题。大多数根源其实是输入数据不光滑,而不是算法本身有问题。
6.4 如果往储能、网络约束方向扩展
这套程序目前每个时段独立求解,没有跨时段的储能SOC约束。如果下一步要加入真正的储能模型,最省事的改法是把单个产消者的子问题从“单变量r”扩展成“24维向量r_t”,目标函数里加上跨时段耦合的SOC状态方程,然后继续用带近端项的分布式迭代。我在一个扩展版本里用quadprog替换了解析解,收敛性几乎不受影响,只是每次子问题求解时间变长。
如果还要考虑配电网线路容量约束,那共享价格就不再是全局统一的λ,而是不同节点有不同的边际电价。此时耦合约束从“∑p_i=0”变成带潮流方程的复杂形式,可以先用线性化DistFlow近似,再通过拉格朗日乘子分解,程序的整体骨架仍然沿用这个Prosumer和Market的分离结构。
这套程序我在好几个算例上都跑过,最实用的一个经验是:第一次运行前,先把参与人数压到2个,手动算一遍这两个人给定价格时各自的解析解,再用程序跑,对照是否一致。这个动作花不了五分钟,但能帮你确认符号约定、边界投影、价格更新方向全都正确。很多奇怪的“不收敛”其实不是算法问题,而是某个地方的符号反了或者边界方向写反了。把这一步做扎实,后面调参的效率会高很多。