
搞过论文复现的人都知道拿到一篇EI论文的题目尤其是带“研究”两个字的第一反应都不是兴奋而是先发愁——这玩意到底能不能复现出来数据哪来模型怎么搭程序跑不跑得通特别是售电市场这种跟电改政策绑得很紧的方向文字描述往往一大堆真正落到Matlab代码上能直接被卡死的细节却一个都不少。我最近刚好完整复现了“售电市场环境下电力用户选择售电公司行为研究”这个课题从读文献、拆模型到写代码、跑仿真、调参数整个过程踩了不少坑也整理出一套比较顺的复现思路。这篇文章就围绕这个EI复现项目把研究方法、数学模型、Matlab实现和排查经验一次性讲透给要做电力市场选题、用户行为仿真的同学一个可以直接上手的参考。先说这个课题是干什么的售电侧放开之后电力用户不再只能从电网公司买电而是可以在多家售电公司之间做选择。用户的这个选择行为直接影响售电公司的市场份额和定价策略是售电市场研究里很核心的问题。复现的目标就是把“用户怎么选售电公司”这件事建一个数学模型然后用Matlab仿真不同场景下的选择结果输出可分析的图表和结论。1. 这个复现项目到底在研究什么1.1 售电侧放开给用户带来的选择权以前电力市场是高度一体化的发输配售一条链用户没有选择权电价也是政府定好的。但售电侧改革之后售电环节被独立出来社会上出现了大量售电公司用户可以自由选择从哪家售电公司购电。这里面的关键点在于用户的“自由选择”不是凭感觉而是一个典型的决策过程。用户会考虑电价高低、服务质量、本公司信誉、电费结算方式、绿色能源占比等因素。不同用户的偏好不一样有的对大用户来说电价降一厘钱都是大事有的商业用户则更看重服务响应速度。这些偏好差异在学术上就可以建模成用户对售电公司各项属性的效用评价。复现这个课题本质上就是把现实中这种复杂的、千人千面的决策过程用数学语言表达出来再用实验数据验证模型的解释能力。理解了这一点整个复现的技术路线也就清楚了先确定用户选择行为的理论模型再设计多场景仿真最后分析仿真结果。1.2 复现论文要解决的关键问题读原文的时候重点关注的是论文用了什么模型框架、设置了哪些场景、得出了什么核心结论。我把复现工作拆成了四个核心问题一是用户选择行为的理论刻画方式。不同论文会采用不同方法比如层次分析法、逼近理想解排序TOPSIS、演化博弈、离散选择模型等。我复现的这篇EI论文采用的是基于随机效用理论的多项LogitMNL模型。二是效用函数如何构建。用户选择售电公司时考虑哪些属性、每个属性权重怎么确定、效用函数的具体形式是什么。这些直接决定仿真结果是否合理。三是仿真场景怎么设置。论文一般不会把所有参数都写全但会根据研究问题设计若干对比场景来描述不同市场环境比如售电公司定价策略变化、用户偏好结构变化、市场成熟度不同等。四是需要输出什么样的结果。通常包括用户选择概率分布、售电公司市场份额变化曲线、关键参数的敏感性分析等。最终就是要用图表复现这些结果验证并支撑对论文的理解甚至在此基础上继续做扩展。2. 方法选型与数学模型拆解2.1 为什么选多项Logit模型在电力用户选择售电公司的研究里多项Logit模型Multinomial Logit Model几乎是“性价比最高”的选择也是这类论文最常用的方法之一。先说直觉。用户面对若干个售电公司本质上是在多个选项里挑一个最“合算”的。但用户的偏好里有太多我们观察不到的因素比如主观好恶、信息渠道差异、历史使用习惯等。如果把这部分全部归入一个随机误差项那用户选择某个售电公司的概率就由一个可观测的效用如电价、服务质量等带来的效用和一个随机项共同决定。MNL模型的数学形式非常优雅假设用户i选择售电公司j的效用为Uij我们只观察到其中的确定性部分Vij随机部分服从极值分布那么选择概率可以写成[ P_{ij} \frac{\exp(V_{ij})}{\sum_{k1}^{J} \exp(V_{ik})} ]这里J是可选售电公司的数量。这个公式的意思是用户选择的概率跟该选项的效用指数成正比跟所有选项效用指数之和相比。跟其他方法比MNL最大的优势是既有微观经济学理论支撑又简单可算而且在Matlab里实现也就是一个softmax事。相比之下层次分析法和TOPSIS都需要人为打分赋权主观性太强不太好做情景对比演化博弈则偏向群体行为演化刻画个体异质性相对弱一些。MNL刚好卡在“理论深度”和“工程可实现性”的平衡点上适合做论文复现。2.2 效用函数怎么构建确定用MNL之后最重要的工作就是构建效用函数Vij。这部分是复现工作的核心也是原文写得最详细、最需要逐句抠的部分。在我复现的这篇论文里用户选择售电公司主要考虑四个属性售电公司的售电价格元/千瓦时服务质量水平用服务响应时间或服务评分表示公司信誉与品牌影响力用信誉评分表示绿色电力比例用绿电占比表示这里有个重要细节这些属性量纲不同不能直接相加必须先做归一化处理。论文里采用的是极差标准化把每个属性值映射到[0,1]区间。我复现时用Matlab自带的mapminmax函数就能实现。归一化之后效用函数的形式是[ V_{ij} \alpha_1 \cdot P_{ij}^* \alpha_2 \cdot S_{ij}^* \alpha_3 \cdot C_{ij}^* \alpha_4 \cdot G_{ij}^* ]其中P是价格取负因为价格越高效用越低S是服务质量C是公司信誉G是绿色电力占比上标*表示归一化后的值。α是各属性的权重系数代表用户对某个属性的重视程度。原文通过问卷调查或预设偏好场景给定了权重复现时就要做参数敏感性分析看权重变化如何影响用户选择结果。这里我踩过一个坑就是市场价格属性取负的问题。如果直接套原始价格数值进效用函数价格越高效用反而越高仿真出来的选择概率完全反了。一定要把价格属性构造成“价格越低越好”的形式我用的是负的价格归一化值。如果是跟成本、价格相关的属性先想清楚“大还是小更受偏好”再决定是不是取负。2.3 仿真场景设计数学模型确定之后就要设计仿真场景了。原文的仿真思路基本可以分成三类场景一是基准场景。把售电公司的各项属性设置为某个合理的基准值模拟市场处于均衡状态时用户的选择分布。这个场景作为对照检验模型能否正常运转。二是政策或市场变动场景。比如某家售电公司主动降低售电价或者提升绿电比例观察用户选择概率怎么变化从而分析售电公司的竞争策略效果。三是用户偏好差异场景。把用户分为价格敏感型、服务敏感型、绿电偏好型等群体赋予不同的权重向量观察不同类型用户的选择差异。仿真场景设置的逻辑是要能突出研究问题、对比不同情形下的结果差异。复现的时候论文给出的场景参数往往不完整需要根据论文图表里的坐标范围反推合理参数。比如原文画了“售电价格从0.4元/千瓦时变化到0.6元/千瓦时市场份额变化曲线”那场景参数就设定在这个区间内逐步扫描。3. Matlab代码复现全流程3.1 代码整体框架与文件分工复现一个带Matlab代码的研究项目最忌讳的是把所有逻辑堆在一个脚本里。我建议按照模块拆分这样排查错误、调整参数、做敏感性分析都会顺手很多。我复现时的代码结构是这样安排的文件/函数功能main.m主程序初始化参数、调用各模块、汇总结果init_params.m初始化所有仿真参数的配置文件gen_users.m生成用户群体设置用户偏好权重、属性值等calc_utility.m计算用户对每个售电公司的效用值calc_prob.m基于多项Logit模型计算选择概率plot_share.m可视化用户选择结果和市场份额main.m的核心结构是这样一个流程初始化参数→生成用户→计算效用→计算选择概率→汇总统计→绘图。%% 初始化 params init_params(); %% 对每个场景进行仿真 for s 1:length(params.scenarios) % 生成用户群体 users gen_users(params, s); % 计算每个用户对每个售电公司的效用 U calc_utility(users, params.retailers, params.weights); % 计算选择概率 P calc_prob(U, params.theta); % 统计市场份额 share(s, :) mean(P, 1); end %% 绘图 plot_share(share, params.scenario_names);这个框架看起来简单但它把“仿真参数”“用户生成”“效用计算”“概率计算”四个关注点分开了。后面调权重、换场景、加属性都不用动主程序只改对应函数就行。3.2 用户群体与场景参数生成用户生成这一步核心是模拟用户的异质性。现实中不同用户对价格、服务、绿电的敏感度不一样建模上就是给每个用户分配不同的权重向量。function users gen_users(params, scenario_id) rng(scenario_id * 42); % 固定随机种子保证结果可复现 n params.num_users; % 用户偏好权重矩阵每行是一个用户对四项属性的权重 % 这里采用正态分布生成模拟偏好差异 users.weights abs(randn(n, 4)) 0.1; % 归一化到和为1 users.weights users.weights ./ sum(users.weights, 2); % 不同场景可以调整均值偏移 if scenario_id 2 % 比如场景2是价格敏感型市场 users.weights(:,1) users.weights(:,1) * 2; users.weights users.weights ./ sum(users.weights, 2); end end这里用randn是为了让用户群体的偏好有差异而不是所有人都同一个权重。加0.1是避免某个权重为0导致用户完全不关注某个属性——这在现实中几乎不可能但在仿真里会导致矩阵奇异或者概率为0很麻烦。售电公司属性的设定也很关键。我一般用一个结构体数组来存每家售电公司的各项属性值params.retailers struct(... name, {公司A, 公司B, 公司C}, ... price, [0.52, 0.48, 0.55], ... % 单位元/kWh service, [7.5, 8.2, 6.9], ... % 服务评分满分10 credibility, [8.0, 7.0, 8.8], ... % 信誉评分 green_ratio, [0.20, 0.35, 0.15]); % 绿电比例3.3 核心选择概率计算逻辑选择概率计算是全部代码的心脏。前面公式里的MNL概率在Matlab里可以直接用向量化的方式实现不需要写成for循环遍历每个用户这样在用户量比较大的时候比如1万个用户也能跑得很快。function P calc_prob(U, theta) % U: 用户×售电公司的效用矩阵 % theta: 效用规模参数相当于logit模型里的标度参数 expU exp(theta * U); % 分母每个用户的效用指数之和沿着公司维度求和 denom sum(expU, 2); P expU ./ denom; end注意一个细节如果用分号的写法还是不行别忽略theta这个参数。theta在MNL模型里控制了效用差异的选择敏感性theta越大用户越倾向于选择效用最高的售电公司theta为0时用户完全随机选择概率等于1/公司数。我在复现时最开始把theta设为1结果仿真出来用户选择行为跟没头苍蝇一样后来调成5才出现明显的选择差异。计算效用矩阵U的时候另一种常见写法是function U calc_utility(users, retailers, ~) % 提取属性矩阵每一行是不同公司列是属性 attr_price -normalize([retailers.price]); % 负值价格越低越好 attr_service normalize([retailers.service]); attr_cred normalize([retailers.credibility]); attr_green normalize([retailers.green_ratio]); % 属性矩阵 attr_mat [attr_price; attr_service; attr_cred; attr_green]; % 每个用户对每个公司的效用 公司属性 × 用户权重向量 % users.weights是 n×4attr_mat是 J×4 % 用矩阵乘法批量计算U(i,j) weights(i,:) * attr_mat(j,:) U users.weights * attr_mat; end这里面有个易错点用户权重矩阵是n行4列公司属性矩阵是J行4列直接乘法会维度不匹配。我上面用的users.weights * attr_mat得到的是n行J列正好每一行是某个用户对所有售电公司的效用。想清楚矩阵维度就能避免一大堆低级报错。3.4 可视化与结果解读仿真的结果最终要靠图表呈现。Matlab绘图的核心是能清晰表达“不同场景/不同参数下的选择概率或市场份额变化”。最常用的图是不同售电公司的市场份额随某个参数变化的曲线。这个图的画法其实很简单对一个参数做扫描每个参数点跑一次仿真把市场份额记录下来最后plot出来。% 对售电价格从0.40到0.60进行扫描 price_range 0.40:0.01:0.60; shares zeros(3, length(price_range)); for k 1:length(price_range) params.retailers(1).price price_range(k); U calc_utility(gen_users(params, 1), params.retailers, []); P calc_prob(U, params.theta); shares(:, k) mean(P, 1); end figure; plot(price_range, shares(1,:), r-o, LineWidth, 1.5); hold on; plot(price_range, shares(2,:), b--s, LineWidth, 1.5); plot(price_range, shares(3,:), g-.^, LineWidth, 1.5); grid on; xlabel(公司A的售电价格元/kWh); ylabel(市场份额); legend({公司A,公司B,公司C}, Location, best);画这种图的时候有个经验线条类型一定要区分清楚实线、虚线、点划线、不同的marker否则打印出来或者放在论文里变成灰度图之后没法区分是哪家公司。另外xlabel和ylabel一定要带单位这是学术论文复现的基本素养。除了曲线图还可以用堆叠柱状图表示不同用户群体对售电公司的选择结构用热力图展示不同权重组合下的选择概率分布。可视化本身不是目的核心是能从图里读出研究结论。我画完图之后都会检查每个图的曲线趋势是否符合直觉比如价格下降市场份额上升如果出现反向趋势一定是代码有bug。4. 复现过程中的踩坑记录与排查手册4.1 典型错误和卡点整个复现过程里我踩了不少坑其中几个特别有代表性。第一个坑是随机数的复现问题。Matlab里如果每次运行都调用rand和randn没有设置随机种子每次跑出来的结果都不一样。论文里你写“固定随机种子可以复现结果”代码就一定要有rng固定种子的语句。我一开始没注意这件事调好参数之后重新跑一遍发现曲线变了还以为程序出问题了折腾了很久才发现是随机种子没固定。第二个坑是效用函数的符号方向。前面提过价格属性取负的问题这个坑我印象太深了。最开始我把归一化后的价格直接加权进效用跑出来的结果是“价格越贵份额越高”一开始以为模型反了后来仔细一想正确做法是对价格取负再参与计算。也就是说效用函数里的价格系数理论上应该是负值用最小值归一化后价格最低的公司会得到效用最高。第三个坑是theta设置不当导致概率几乎均匀分布。很多MNL复现的教程里不会强调theta的重要性但对于读代码的人来说直接套用论文公式是看不出这个问题的。实际仿真时如果theta太小如0.1所有概率都接近1/3看不出变化趋势如果theta太大如100概率分布又过于极端几乎变成确定性选择。建议theta在1到10之间多试几个值选出效果最合理的。第四个坑是用户数量太少导致统计波动大。如果用户只生成50个选择概率算出来是跳跃的曲线的毛刺非常多。我后来把用户量加到2000以上市场占有率的曲线才比较平滑。Matlab里计算2000用户的选择概率也就是矩阵运算一瞬间的事没必要用太少用户数。4.2 常见错误速查表结合复现过程我整理了一张常见错误排查表对新手特别有用症状可能原因解决思路运行报错“矩阵维度不一致”用户权重矩阵与公司属性矩阵维度不匹配统一属性数量检查是否多算或少算了一列市场份额出现NaN或Inf效用值过大exp()溢出检查属性归一化是否做对theta是否调得过大所有概率几乎相同theta值太小用户偏好区分度不够增大theta或增加用户群体权重差异市场份额曲线毛刺多用户数量太少增加用户数量到2000以上价格越贵份额越高价格属性没取负价格类属性取负后再加入效用函数每次运行结果不一样没有设置随机种子在main.m开头加rng(固定数值)不同场景结果无法对比场景间用户生成没有固定对应关系给每个场景分配不同的随机种子保证场景间可区分但可重复4.3 如何判断复现结果是否可靠判断复现结果是否可靠我的经验是从三个层次来检查。第一层是检查数值范围是否合理。选择概率应该在0到1之间而且每一行的和应该等于1。如果一个用户对三家公司的概率加和不是1那计算概率的代码一定有bug。这个检查我用一行代码就搞定assert(all(abs(sum(P,2) - 1) 1e-8))。第二层是检查单调性是否符合经济学直觉。降价的售电公司市场份额应该上升绿电比例升高的公司在绿电偏好型用户群体中的选择概率应该明显增加。如果这些直觉关系不成立说明效用函数或者权重设置有误。第三层是检查结论是否匹配原文。EI论文复现不是要“完全数字一致”——因为论文里很多随机种子、具体参数是不公开的但要保证趋势和核心结论一致。比如原文说“价格因素对用户选择影响显著”那复现结果应该体现价格变化引起市场份额较大波动。如果趋势方向一致数值上有合理差异这个复现就是成功的。5. 个人实操经验与扩展建议整个项目跑通之后我最大的感受是复现EI论文的价值不在于把论文的图和表原封不动地生成一遍而在于通过写代码、调参数、看结果真正理解每个模型假设背后的原因。比如多项Logit模型为什么用指数函数形式为什么随机项假设为极值分布这些在教科书上只是一行公式但自己实现一遍之后才会理解它对仿真的影响是什么。给正在做类似课题的同学几个具体建议第一不要上来就埋头写代码先把论文的模型图和公式推导过一遍。论文里没有明确的参数就标注出来根据仿真结果反向推合理范围。第二Matlab代码的格式和注释宁可多写不要少写。这不仅是说给其他人看也是在复现结束后你会感谢自己当时的注释。这个项目做完隔两周再看代码如果没有注释真的会忘掉每一个矩阵的具体含义。第三参数配置文件一定要单独放。把仿真参数集中在一个init_params.m里改参数的时候不需要到处翻代码也能避免改了一个地方忘了另一个地方的尴尬。第四画图的时候把关键图表保存成高分辨率的图片格式字体设置一致方便后面直接用于论文写作。我常用exportgraphics(gcf, output.png, Resolution, 300)这样导出的图片放到Word里也是清晰的。如果还想扩展这个项目可以考虑往三个方向做一是把MNL模型换成混合Logit模型考虑用户偏好的随机系数更细腻地刻画用户异质性二是引入售电公司之间的博弈比如两家公司同时调价时的市场均衡三是结合真实用电数据对用户群体进行聚类分析再给每一类用户构建选择模型这个跟热点词里大家常搜的kmeans聚类算法也能衔接上。最后一点点小技巧也是我复现到后期才领悟的务必用版本管理工具跟踪脚本的每次修改。自己在调参过程中会把好的参数试坏回退版本比逐个手改要高效很多。哪怕不习惯用复杂的git操作隔一段时间把当前能正常跑的脚本复制一份备份在文件名上加上日期也比最后找不到可用版本强得多。