前一阵有个师弟问我,“基于分布式优化的多产消者非合作博弈能量共享”这类题目到底在研究什么,Matlab代码又该从哪下手。我一听就明白他卡在哪了——这类工作横跨电力系统、博弈论和最优化三个方向,从数学模型到可运行的代码,中间藏着不少隐性门槛。本文我就以自己做过的一套仿真实验为例,把这个题目的来龙去脉、建模思路和代码框架一次讲清楚,顺便把调试时踩过的坑也一并交代。
这套内容适合三类人:一是刚接触产消者能量共享方向的研究生,想快速理解题目逻辑;二是已经在做分布式优化,但搞不清非合作博弈和ADMM之间关系的开发者;三是想把论文里的数学公式变成能出图的Matlab程序,但不知道从哪里入手的工程师。读完之后,你至少能回答三件事:多产消者为什么要用非合作博弈建模,内部电价协调的分布式求解思路怎么落地,以及一套可以直接改参数运行的Matlab代码骨架长什么样。
1. 问题背景与整体研究思路
1.1 什么才算“多产消者”场景
先说清楚研究对象。所谓产消者,就是集生产与消费于一身的主体:屋顶装了光伏的家庭、装了储能的小型工商业用户、拥有充电桩的车主,都算典型产消者。以前电力系统是“发电厂集中发电、用户单向用电”,但当一片区域里出现大量分布式光伏和储能之后,潮流开始双向流动,用户之间的能量交互在物理上已经可行。
能量共享要做的事情,就是让一个社区、一栋楼或一个园区内部的产消者,优先互相匹配供需:光伏大发时,有余量的用户把电共享给缺电的邻居,而不是全部反送电网;夜间负荷高峰时,大家从内部买电而不是全部从主网购电。这样既能降低对输配电网的依赖,也能减少整体购电费用。
在这个方向上,典型的研究规模是4到10个产消者、24小时调度周期,时间步长取1小时。这个规模在Matlab里求解毫无压力,也是大多数论文验证算法有效性的起步配置。如果直接把规模提到50个节点、时间步长降到15分钟,计算量会明显上升,但算法框架不会变,这也是这个方向很适合用Matlab做快速验证的原因。
1.2 为什么用“非合作博弈”,而不是集中式调度
这个问题几乎每个刚接触该方向的人都会问。集中式调度的思路是:假如所有产消者愿意把光伏出力、负荷曲线、储能状态全部上报给一个中心节点,中心求解全系统总成本最小化问题,然后下发每个产消者的最优充放电指令。理论上这能做到系统级最优,总成本最低,实现也简单。
但现实中没有几个产消者愿意这么做。大家本来就是独立的主体,设备参数属于私密信息,负荷曲线牵扯生活习惯,储能容量更不可能随便告诉别人。就算统一调度能省电费,因为没有从属关系和强制力,这套方案在执行层面根本推不下去。
非合作博弈恰好承认每个参与者的“自利”属性。它不要求任何人把私有数据交给中心,而是设定一个公平的交互机制,让每个产消者在给定外部信号(比如内部共享电价)时,独立优化自己的成本。所有产消者各自做决定,最终在市场机制下收敛到一个大家都能接受的均衡点。这套逻辑和现实行为更贴合。
有个生活类比很适合理解这件事:几个房东共用一台变压器,每家都想多开空调又少交电费。如果让一个物业经理统一调度所有人的用电计划,理论上最省,但大家根本不会听。现实是物业根据总负荷调节内部电价,每家根据电价信号自己决定怎么用电,最后形成一个“虽然不完美但大家都能接受”的状态。非合作博弈就是给这种“各自决策”提供一套严格的数学框架。
1.3 从实际问题到数学模型要回答的三个问题
把实际问题变成可计算的模型,核心要回答三个问题。参与者是谁,每个参与者的策略空间是什么,收益或效用怎么量化。
在这类能量共享研究中,参与者就是N个产消者;策略空间是每个产消者关于储能充放电、电网购售电和参与共享电量的决策;效用函数则是调度周期内的总成本,包括购电费用、售电收益和内部共享交易费用。这三个要素就构成了博弈模型的基础。
完成这一步之后,后面所有代码都围绕这三个要素展开。模型的分寸拿捏在“简单但不过度简化”:既要保留产消者之间的耦合关系,又不能把设备模型搞得比实际工程需求更复杂。储能模型用一阶SOC更新,光伏和负荷用时间序列直接给定,电网购售电用分时电价,这样既能反映核心问题,又不至于让代码被无关细节淹没。
2. 核心数学模型搭建
2.1 单个产消者的物理模型
先定义基本变量。设产消者数量为N,调度时段为T,通常取24。每个产消者i在时段t的光伏出力用(P_{pv}(i,t))表示,负荷用(P_{load}(i,t))表示,与电网的交互分为购电(P_{grid_buy}(i,t))和售电(P_{grid_sell}(i,t)),与内部市场的共享功率用(P_{share}(i,t))表示,正值代表买入,负值代表卖出。
储能模型是这个问题的核心。设储能能量状态为(E(i,t)),充放电功率分别为(P_{ch}(i,t))和(P_{dis}(i,t)),那么SOC更新方程为:
[ E(i,t+1)=E(i,t)+\eta_{ch}P_{ch}(i,t)\Delta t-\frac{P_{dis}(i,t)}{\eta_{dis}}\Delta t ]
约束条件包括储能容量上下限、充放电功率上下限,以及同一时刻不能同时充放电。光伏出力和负荷曲线在仿真中通常按时间序列直接读入,不用建模光伏板的物理发电过程,因为能量共享研究关心的是调度层策略,不是光伏组件电气层特性。
这里有个新手几乎必踩的坑:时间步长(\Delta t)不是1的时候,SOC更新方程里一定要记得乘上。很多人把时间单位写成小时,采集间隔却是15分钟,结果SOC变化量被放大了4倍,最终的充放电策略完全不合理。我用kWh做能量单位,功率用kW,时间步长直接用小时数值参与运算,这样量纲最直观。
2.2 目标函数与成本构成
每个产消者i的目标函数,是在一个调度周期内最小化自己的总成本:
[ J_i=\sum_{t=1}^{T}\left[\pi_b(t)P_{grid_buy}(i,t)-\pi_s(t)P_{grid_sell}(i,t)+\pi_{share}(t)P_{share}(i,t)+\beta||P_{bat}(i,t)||^2\right] ]
其中(\pi_b(t))是分时购电价,(\pi_s(t))是售电价,(\pi_{share}(t))是内部共享电价,(\beta)是储能损耗惩罚系数。
内部共享电价(\pi_{share}(t))是整个问题的灵魂变量。它不由外部给定,而是在迭代求解过程中动态调整,最终收敛到一个同时满足所有产消者最优性和市场出清条件的值。物理上它也应该落在购电价和售电价之间:如果共享电价高于购电价,产消者宁可向电网买电也不会参与内部共享;如果低于售电价,产消者宁可卖给电网也不愿内部卖出。这是后续判断仿真结果是否合理的第一道检验标准。
储能损耗项(\beta||P_{bat}||^2)不是简单锦上添花,它在数值计算中起着很关键的作用。目标函数里如果没有这个二次项,子问题在某个方向上会存在平坦区域,迭代时解容易来回漂移;加了它之后,每个本地问题变成严格凸问题,最优解唯一,迭代行为明显稳定。实际使用中(\beta)取0.01到0.05就能达到效果,不必太大。
2.3 耦合约束与博弈均衡的数学表述
产消者之间能量共享的本质约束是功率守恒:每个时段所有产消者的共享功率之和必须为零。
[ \sum_{i=1}^{N}P_{share}(i,t)=0,\quad \forall t ]
这组等式就是耦合约束,它把各个独立的产消者捆绑在一起。没有它,每个产消者都是单机优化,整个问题退化为N个互不相干的最小化问题,研究也就没有意义了。正是这组约束的存在,使得我们需要分布式优化来协调各方。
从博弈论角度,我们把均衡条件形式化:存在一个内部共享电价向量(\pi_{share})和一个策略组合,使得每个产消者都在给定电价下实现了自身成本最小化,同时市场出清约束成立。满足这两个条件,就得到一个广义纳什均衡。
这个表达方式的最大好处是,把难以直接求解的博弈均衡问题,转化成了一个相对直观的“搜索合适电价”的问题。每个产消者只需针对给定价格求解自己的凸优化问题,然后由协调层根据总供需偏差更新价格,循环往复直到出清。这就是分布式优化的用武之地。
3. ADMM与价格协调:分布式求解算法和Matlab实现
3.1 为什么选ADMM思路,而不是直接解方程组
从数学上看,均衡条件可以写成所有子问题KKT条件加市场出清约束的联立方程组,理论上可以直接求解。但实际这样做的代价很高:决策变量维度会随着产消者数量线性增长,约束矩阵的规模迅速膨胀,而且集中式求解把每个产消者的私有信息全部汇总到一处,又回到了隐私问题的老路。
ADMM的分解-协调思想正好对症。它把大问题拆成每个产消者自己解决的小问题,再通过一个协调层把大家的解拉回一致。在这个能量共享模型里,协调层只需要维护一个变量,就是内部共享电价向量(\pi_{share})。
迭代过程分为三步。第一步,给定当前共享电价,所有产消者并行求解自己的本地优化问题;第二步,汇总所有人的共享功率,计算每个时段的总供需不平衡量;第三步,用不平衡量修正共享电价,然后判断是否收敛。这个机制在经济学上对应“摸索出清价格”的过程,在数学上等价于用带惩罚的对偶上升法求解耦合约束下的均衡问题,也就是ADMM的一种工程化变体。
3.2 价格更新与收敛性设计的几个关键参数
价格更新公式是分布式求解的核心:
[ \pi_{share}^{(k+1)}(t)=\pi_{share}^{(k)}(t)+\rho\Delta^{(k)}(t) ]
其中(\Delta^{(k)}(t)=\sum_{i=1}^{N}P_{share}^{(k)}(i,t))是第k轮迭代时段t的总共享功率不平衡量,(\rho)是步长。
步长大小直接影响收敛行为。(\rho)过大,价格更新时容易过度修正,表现为价格来回震荡或者呈发散趋势;(\rho)过小,迭代推进太慢,需要跑几百轮才能收敛。实践中我一般先取0.1到0.5之间的数值,跑100轮画出收敛曲线,再根据情况调整。更稳妥的做法是对不平衡量做归一化,把(\Delta(t))除以系统内各产消者最大共享功率之和,这样步长的取值范围与系统规模解耦,换一组数据不用重新调参。
收敛判据设置也要合理。主判据是每个时段的最大不平衡量小于一个阈值,比如总负荷的1%以内;辅助判据可以看相邻两轮共享电价的变化量,如果已经很小,说明迭代接近停滞点,可以提前结束。
共享电价还要设置上下限,一般取购电价之上加一点余量、售电价之下减一点余量。上限设成购电价的1.2倍,下限设成售电价的0.8倍,可以防止数值异常时价格跑到不合理的区间。
3.3 Matlab代码框架与核心函数实现
我的代码组织方式是主脚本加三个子函数,文件结构如下:
ADMM_energy_sharing_main.m generate_data.m prosumer_subproblem.m update_shared_price.m plot_results.m主脚本负责初始化参数、启动迭代循环、保存每轮结果。核心循环的Matlab代码如下:
%% 初始化 N = 4; % 产消者数量 T = 24; % 调度时段数 dt = 1; % 时间步长,单位:小时 caseData = generate_data(N, T); buy_price = caseData.buy_price; % 1x24 购电价 sell_price = caseData.sell_price; % 1x24 售电价 price_shared = ones(1,T) * (buy_price(1) + sell_price(1)) / 2; rho = 0.3; % 价格更新步长 tol = 0.01; % 收敛阈值 maxIter = 200; residual = zeros(maxIter, 1); history = zeros(maxIter, T); %% ADMM 主循环 for k = 1:maxIter P_share = zeros(N, T); P_grid = zeros(N, T); P_bat = zeros(N, T); cost_i = zeros(N, 1); % 各产消者独立求解本地子问题 for i = 1:N [P_share(i,:), P_grid(i,:), P_bat(i,:), cost_i(i)] = ... prosumer_subproblem(i, price_shared, caseData); end % 计算市场出清不平衡量 imbalance = sum(P_share, 1); residual(k) = max(abs(imbalance)); % 更新内部共享电价 price_shared_new = price_shared + rho * imbalance; price_shared = min(max(price_shared_new, 0.8*min(sell_price)), 1.2*max(buy_price)); % 记录收敛过程 history(k, :) = price_shared; % 收敛判定 if residual(k) < tol fprintf('第%d轮收敛,残差=%.4f\n', k, residual(k)); break; end end子问题函数是整个代码的核心。产消者i给定当前共享电价,求解自己的最小成本问题。我强烈建议直接用quadprog,而不是fmincon,原因很实际:这个子问题的目标函数是二次型,约束全是线性等式和不等式,属于标准凸二次规划,quadprog求解速度极快;fmincon通用性强但对这类问题过度浪费,在几十次ADMM迭代里会明显拖慢整体速度。
子问题函数内部的变量定义和约束构造思路如下,决策变量按顺序排列:
function [P_share, P_grid, P_bat, cost] = ... prosumer_subproblem(i, price_shared, caseData) T = length(price_shared); pv = caseData.pv(i,:); load_ = caseData.load(i,:); E0 = caseData.E0(i); % 储能初始能量 Emax = caseData.Emax(i); Emin = caseData.Emin(i); PBmax = caseData.PBmax(i); eta_ch = caseData.eta_ch; eta_dis = caseData.eta_dis; buy = caseData.buy_price; sell = caseData.sell_price; beta = 0.02; % 决策变量顺序 % 1:T P_share % T+1:2T P_grid_buy % 2T+1:3T P_grid_sell % 3T+1:4T P_bat_ch % 4T+1:5T P_bat_dis % 5T+1:6T-1 E(2) 到 E(T),储能中间状态变量 nvar = 6*T - 1; % 目标函数二次项:储能充放电的惩罚项 H = zeros(nvar, nvar); for t = 1:T H(3*T+t, 3*T+t) = beta; H(4*T+t, 4*T+t) = beta; end % 目标函数一次项 f = zeros(nvar, 1); f(1:T) = price_shared(:); % 共享电费 f(T+1:2*T) = buy(:); % 购电成本 f(2*T+1:3*T) = -sell(:); % 售电收益,负值表示降低成本 % 等式约束矩阵 Aeq 和 beq % 1) 功率平衡每时段一行,共 T 行 % 2) SOC 更新方程每时段一行,共 T-1 行 % Aeq 的行数 = 2*T - 1 Aeq = zeros(2*T-1, nvar); beq = zeros(2*T-1, 1); % 功率平衡:buy - sell + dis - ch + pv - load + P_share = 0 for t = 1:T Aeq(t, t) = 1; % P_share Aeq(t, T+t) = 1; % P_grid_buy Aeq(t, 2*T+t) = -1; % P_grid_sell Aeq(t, 3*T+t) = -1; % P_bat_ch Aeq(t, 4*T+t) = 1; % P_bat_dis beq(t) = load_(t) - pv(t); end % SOC 更新:E(t+1) = E(t) + eta_ch*P_ch*dt - P_dis/eta_dis*dt for t = 1:T-1 row = T + t; if t == 1 Aeq(row, 5*T+1) = 1; % E(2) % 初始状态项移到右侧 Aeq(row, 3*T+t) = -eta_ch * dt; Aeq(row, 4*T+t) = dt / eta_dis; beq(row) = E0; else Aeq(row, 5*T+t) = 1; % E(t+1) Aeq(row, 5*T+t-1) = -1; % E(t) Aeq(row, 3*T+t) = -eta_ch * dt; Aeq(row, 4*T+t) = dt / eta_dis; beq(row) = 0; end end % 边界约束 lb = zeros(nvar, 1); ub = zeros(nvar, 1); lb(2*T+1:3*T) = 0; ub(2*T+1:3*T) = PBmax; % P_grid_sell lb(T+1:2*T) = 0; ub(T+1:2*T) = PBmax; % P_grid_buy lb(3*T+1:4*T) = 0; ub(3*T+1:4*T) = PBmax; % P_bat_ch lb(4*T+1:5*T) = 0; ub(4*T+1:5*T) = PBmax; % P_bat_dis ub(5*T+1:6*T-1) = Emax; lb(5*T+1:6*T-1) = Emin; % 储能中间能量 % P_share 的正负由共享电价和购售电价共同决定,物理可正可负 lb(1:T) = -PBmax; ub(1:T) = PBmax; % 求解 options = optimoptions('quadprog', 'Display', 'off'); x = quadprog(H, f, [], [], Aeq, beq, lb, ub, [], options); if isempty(x) error('第%d个产消者的子问题无解,请检查约束设置或初值', i); end P_share = x(1:T)'; P_grid = x(T+1:2*T)' - x(2*T+1:3*T)'; P_bat = x(3*T+1:4*T)' - x(4*T+1:5*T)'; E_traj = [E0, x(5*T+1:6*T-1)']; cost = x' * H * x / 2 + f' * x; caseData.E_traj(i,:) = E_traj; end注意一点:如果把H矩阵的二次项两边都有1/2因子,写成cost时要注意quadprog的目标是0.5*x'Hx+f'*x,所以最后cost表达式的0.5因子不能漏。这个问题我调试时花了不少时间,代码里注释要写清楚。
3.4 为什么推荐先用小型算例验证逻辑
我强烈建议拿到代码先跑一个N=2的最小场景:一个产消者光伏大发、自身负荷小,另一个正好相反,夜间负荷很大但没装光伏。这个场景下手工就能推算出大致结论:共享电量会从有余电的一方流向缺电的一方,共享电价会落在购电价和售电价之间,两个产消者的总成本应当比各自单干更低。
用这样的最小场景验证逻辑,有两个好处。一方面,规模小,出问题容易定位,是不收敛、价格越界还是SOC约束写错,通过打印中间变量一眼就能看出来;另一方面,它给后续大规模仿真提供了一个可信基准——如果双产消者场景都解释不通,扩展到10个产消者只会更难排查。
4. 仿真结果分析与参数调节经验
4.1 从输出曲线里应该看到什么
仿真跑完之后,至少要画四类图:各产消者的储能SOC曲线、内部共享电价变化曲线、每位产消者的购售电与共享功率分布图、算法收敛残差曲线。
正常的仿真结果应该呈现几个特征。光伏高峰时段,有光伏余量的产消者共享功率为负,把电卖给内部市场;夜间负荷高峰时段,缺电的产消者共享功率为正,从内部买入。储能SOC曲线应当呈现削峰填谷的逻辑,光伏充足时充电、夜间放电,而不是毫无规律地随机充放。
内部共享电价曲线应该始终位于购电价和售电价之间。如果某时段共享电价高于购电价,产消者宁愿从电网买电,市场机制在这个时段就失效了;如果低于售电价,产消者宁肯把电卖给电网,也不会参与内部共享。这个不等式检查是结果合理性判断的第一关。
成本对比也很重要。分别跑“不参与共享”和“参与共享”两个场景,比较区域总购电成本。通常参与共享后,区域整体购电成本会下降,因为部分光伏余量在内部就被消纳了,减少了从主网购电的量。但要注意,博弈均衡并不保证每个产消者的成本都比单干时低,在非合作框架下,如果一个产消者因为自身约束太紧而无法套利,可能出现成本不降反升的情况,这是正常的博弈行为,不是代码bug。真遇到这种情况,需要检查是否因为价格初值或上下限设置把某类产消者的可行域逼得太紧。
4.2 收敛性调试:步长、上下界与初始点
收敛性问题是这个项目里调试时间最长的一部分。最典型的现象是共享电价一直上涨但不收敛,这时候第一反应是检查步长(\rho)是不是太大。我把(\rho)从0.3减到0.1,价格震荡幅度立刻小了很多,虽然迭代次数变多了一些,但整体稳定了。
另一个常见问题是共享电价在上下界之间来回弹。这种震荡往往不是步长问题,而是价格上下限设置过窄。把上限从1.2倍购电价放宽到2倍,给市场更大的调节空间,震荡往往就消失了。
还有残差下降速度极慢的情况,特征是前50轮下降很快,之后数百轮几乎不动。这时候将步长整体放大10倍再看,如果残差曲线变陡了,说明步长本来就太小。也可以配合在价格更新公式里加入动量项,吸收ADMM里的惯性思想,能显著加快收敛。
共享电价的初始值也不容忽视。如果初始设置恰好等于购电价,那么所有产消者在第一轮都没有卖出动力,因为卖出价格和卖电网一样,还要承担额外麻烦。我在代码里统一用购电价和售电价的平均值做初值,实测下来各类场景的收敛表现都稳很多。
5. 常见问题与实操避坑记录
5.1 常见问题速查表
我把调试过程中遇到的高频问题整理成了速查表,供读者直接对照排查。
| 现象 | 可能原因 | 排查与解决方法 |
|---|---|---|
| ADMM迭代不收敛,价格来回震荡 | (\rho)过大或价格上下界过紧 | 将(\rho)减半,放宽(\pi_{share})上下限,必要时做价格平滑 |
| 每个产消者成本都高于预期 | 储能末状态约束过严 | 把严格等式改为(E(T+1)\geq E0)的软约束,给优化留自由空间 |
| 收敛后共享功率不平衡量为零,但电量对不上 | 时间步长(\Delta t)忘记乘 | 检查SOC更新方程,统一使用kWh与kW,让量纲自洽后再验证平衡方程 |
| 共享电价卡在边界处 | 上下限不合理或初始价格不当 | 打印各时段购售电价,重新设定(\pi_{share})限值 |
| 某个产消者一直不参与共享 | 初始共享电价低于其售电价或高于其购电价 | 改用购售电价中位数初始化,检查该产消者的可行域 |
| quadprog提示等式约束不兼容 | 约束矩阵存在线性相关行 | 检查Aeq是否包含冗余行,确保SOC更新的各行线性独立 |
| fmincon求解子问题非常慢 | 非凸求解器处理凸二次规划,性能浪费 | 改为quadprog,或使用YALMIP建模但关掉调试输出 |
5.2 储能SOC初末状态约束的坑
储能约束是这类问题里最容易出“看似合理但结果怪异”的地方。很多版本把E(1)设成50%容量,然后强制要求E(T+1)=E(1),意思是每天回到同样的起始状态。这个约束从长期运营角度没问题,但会让储能完全失去跨日套利能力,因为最后一天晚上必须存够电量回到初始值,导致蓄意回购高价电,成本反而升高。
我常用的做法是末状态用不等式约束(E(T+1)\geq E0),让优化自由决定末期电量,只要不低于初始值即可。这样做既保证了系统的长期可持续性,又给了储能足够的调度灵活性。若期刊或报告要求严格的每日循环,再改成等式约束,同时缩小末时段负荷的波动幅度,避免出现不必要的回购。
5.3 子问题用YALMIP还是quadprog
如果你只是想快速验证模型,YALMIP确实省心,代码可读性也好。但在这个项目里,ADMM主循环需要反复调用子问题几百次,YALMIP每次建模都要重新生成符号变量和约束矩阵,这个开销在多次迭代后会变得不可忽视。
我的选择是:原型验证阶段用YALMIP把模型逻辑写清楚,确认无误后重写成quadprog形式。两种方式我都提供过,代码逻辑一样,但quadprog版本在同样的收敛条件下运行速度快了将近一倍。实验室跑大规模场景时,这个差异直接决定了调参效率。
5.4 数据生成阶段容易忽略的时间对齐问题
光伏出力数据可能来自气象站分钟级记录,负荷数据可能来自历史电表,采样频率不一致时,必须用interp1或resample统一到同一时间轴再进入模型。否则功率平衡方程在边界时段必然报错,而且这种错误往往不会在第一天运行时暴露,直到某一次SOC约束恰好紧绷时才跳出来,极难排查。
另一个实用建议是给数据生成函数设置固定随机种子。不同随机种子生成的光伏、负荷曲线差异很大,有些参数组合下收敛快,有些收敛慢,如果不固定种子,很难判断你调好的参数是不是只在某一组数据上有效。固定种子之后,同一组数据重复跑,结果完全可复现,调参体验会好很多。
最后再分享一个小技巧
调试这类分布式优化代码,我最大的经验是分阶段验证,千万不要一上来就跑到N=10的大规模场景。先把N设为1,单独验证单个产消者的子问题求解是否正确,检查SOC曲线是否合理,目标函数是否满足功率平衡;再把N设为2,用一个“光伏富余+负荷缺电”的算例验证共享电价的收敛逻辑;都通过之后,再把N逐渐增大,观察收敛残差和总成本的变化曲线。
这个逐级放大的调试流程,能帮你把“数学模型推导错误”“凸优化建模错误”和“分布式算法参数问题”这三类错误区别开。如果不是一级一级排查,当这几种错误混在一起时,调试成本会成倍上升。这套流程在我后续做很多类似优化课题时也一直在用,可以说是从踩坑里换来的最值得分享的经验。