模拟数据,真实学习:模拟系统
原文towardsdatascience.com/simulated-data-real-learnings-simulating-systems-79374a9379fdhttps://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/f1aafdddc2303720649a7c1fb444be2f.png来自 Pexels.com 的 Zhang Kaiyv引言模拟是数据科学工具箱中的一个强大工具。在这篇文章中我们将讨论模拟系统如何帮助我们制定更好的策略和做出更好的决策。本文的具体主题是什么是系统模拟系统模拟优化系统模拟风险评估这是关于数据科学中模拟的多部分系列文章的第四部分。第一篇文章介绍了如何使用模拟来测试机器学习方法第二篇文章介绍了如何使用模拟来估计设计实验的功效第三篇文章讨论了我们可以通过模拟情景来制定策略。这里是那些文章的链接模拟数据真实学习第一部分模拟数据真实学习功效分析模拟数据真实学习情景分析什么是数据模拟第一篇文章花了很多时间来定义模拟。参考那里进行更深入的关于模拟是什么的讨论。为了避免重复我这里只给出一个简短的定义数据模拟是创建模仿现实世界特性的虚构数据。好的把这个问题解决了让我们来谈谈系统模拟系统建模在许多领域数据科学家需要回答有关系统的问题。系统是一组相互连接的组件旨在实现一个目标。系统无处不在——航空公司运营、供应链、公共交通、电气和管道系统——仅举几例。当我们模拟系统时我们需要对系统的工作方式有一个很好的理解对系统工作方式的“非常好”的理解。我们需要对系统的组成部分及其连接方式有一个扎实的理解。我们可以通过咨询业务伙伴或领域专家或者通过成为/成为领域专家自己来实现这一点没有规则说数据科学家不能成为他们领域的专家和数据科学专家。https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/0c4d3c2d2a6fe48edffeb7b1229e82c2.png系统模拟有两个主要元素1模拟系统本身和2模拟数据以通过系统。一旦我们很好地理解了系统的框架我们就可以使用我们喜欢的语言编写一个模拟系统。然后我们可以模拟任何我们想要通过系统的数据。这项练习的价值在于从我们输入到系统的模拟数据中收集输出 KPI。我们可以模拟任何我们可以想象到的有效输入并且我们可以得到在模拟输入下观察到的 KPI 的估计。这可以非常强大通过调整模拟系统和模拟数据我们可以获得许多有价值的见解。使用模拟数据进行的系统建模通常用于1优化和2风险评估。我们将在下一部分讨论这些要点在我们继续之前有一个免责声明。有时模拟可能感觉像是作弊——因为我们只是在制造数据——而且如果我们在确保我们的模拟基于合理的假设方面不够彻底它确实可以是。记住我们产生的见解只与我们创建的模拟一样好。垃圾输入垃圾输出在使用结果进行决策之前确保你和你的利益相关者已经彻底验证了用于模拟的假设。好了严肃的话说够了让我们设置一个示例然后讨论我们如何使用系统模拟进行优化和风险评估系统建模模拟示例杂货店排队文章的其余部分将通过示例展示系统模拟的力量。我是 2000 年代初电视节目《神探夏洛克》的忠实粉丝。我从这个节目的第 14 季第 5 集的实验中获得了灵感。在这一集中他们比较了两个杂货店排队设置。我们将使用模拟系统而不是《神探夏洛克》使用的物理系统来测试这两个设置。这里是设置我们想要比较两种不同的杂货店排队配置1多个单收银台的排队线和2一个单排队线带有多个收银台。第一种配置为每个收银台都有一个排队线第二种配置有一个单排队线通向多个收银台——请参见下面的视觉表示。你可能在你购物体验中见过这两种配置。https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/eeaa3122d5a05a9f2aa87a719069ac06.png杂货店排队系统的图形表示——矩形是收银台圆形是顾客——作者提供我们将使用 Python 进行系统模拟。我们将跟踪的主要指标是最后一位排队顾客的等待时间。因此代码将填充队列或队列中的顾客这些顾客有随机的结账时间扫描项目和付款所需的时间然后跟踪最后一位排队顾客的等待时间。这里是模拟多个排队、多个收银台的函数defmultiple_lines_trial(number_lines,customers): Simulates the wait time of the last customer in line for a check out configuration with a line for each register. Inputs: number_lines (int) : number of lines and registers at checkout customers (list) : list of customers checkout times, one time for each customer Returns: total_wait_time (float) : total time that the last customer waited to get to a register # make empty list of lists, one list for each register to# hold customerslines[[]foriinrange(number_lines)]# remove last customer since we are calculating the time# the customer waits to get to the register - ignore# that customers actual check out timecustomers_cpcopy(customers)last_customercustomers_cp.pop()# each customr goes to the shortest line they seewhilecustomers_cp:# add customer to shortest linetemp_custcustomers_cp[0]customers_cpcustomers_cp[1:]line_length_list[len(lines[i])foriinrange(number_lines)]shortest_lineline_length_list.index(min(line_length_list))lines[shortest_line].append(temp_cust)# last customer picks the shortest line as well,# total checkout time of shortest line at the end is the# last customers wait timeline_length_list[len(lines[i])foriinrange(number_lines)]shortest_lineline_length_list.index(min(line_length_list))total_wait_timesum(lines[shortest_line])returntotal_wait_time这里是模拟单条排队、多个收银台的函数defsingle_line_trial(cashiers,customers): Simulates the wait time of the last customer in line for a check out configuration with a single that feeds to multiple registers. Inputs: number_lines (int) : number of lines and registers at checkout customers (list) : list of customers checkout times, one time for each customer Returns: total_wait_time (float) : total time that the last customer waited to get to a register # empty list of wait timeswait_time[]#make list of customers at checkoutcustm_at_checkoutcustomers[0:cashiers]#cut out number of cashiers from customers that are now at registercustomerscustomers[cashiers:len(customers)]#go thru each customerforiincustomers:#pick shortest time at checkoutdone_at_checkoutmin(custm_at_checkout)#get index of customer that is donedone_indexcustm_at_checkout.index(done_at_checkout)#subtract shortest time from each customer at checkoutforj,custinenumerate(custm_at_checkout):custm_at_checkout[j]-done_at_checkout#remove customer that is donecustm_at_checkout.pop(done_index)#add the next customer in line at registerifcustomers:custm_at_checkout.append(customers[0])else:# if no customers left, exitbreak# remove customer that is now at register from waiting customerscustomerscustomers[1:]#add waiting time to listwait_time.append(done_at_checkout)#sum total wait timetotal_wait_timesum(wait_time)returntotal_wait_time好的现在我们有了模拟单个顾客在两种配置下的等待时间的代码当然我们不想仅通过模拟单个顾客的经历来做重大决策。让我们编写一些代码来模拟多个顾客的等待时间。这里是迭代模拟单个顾客等待时间以创建多个顾客等待时间的代码defrun_trials(cashiers,customer_number,num_trials,line_type_func): Runs multiple customers through simulation and gather each runs wait time. Inputs number_lines (int) : number of lines and registers at checkout customers (list) : list of customers checkout times, one time for each customer num_trials (int) : number of simulation iterations to run line_type_func (function) : the function that corresponds to the grocery line configuration simulation Returns cust_wait_times (list) : list of customer wait times, one wait time for each simulation cust_wait_times[]# create list of customer check out durations based on exponential distcustomerslist(np.random.exponential(0.5,customer_number))# run multiple trialsfor_inrange(num_trials):temp_waitline_type_func(cashiers,customers)cust_wait_times.append(temp_wait)returncust_wait_times现在我们的系统模拟已经全部设置完成。下一步是开始运行模拟以做出最优决策并了解我们的系统所面临的风险关于假设的简要说明 – 在我们编写系统模拟的方式中我们做出了多个假设。例如我们假设顾客结账时间服从以 0.5 为λ的指数分布我们假设当有多个排队时顾客总是去最短的排队所有收银员都有相同的熟练度等等。确保你理解你所做的假设一些假设隐含在你的模拟设计选择中确保你仔细思考你的设计以发现和理解它们并且你对此感到满意。优化好的所以一切准备就绪我们可以开始生成见解了正如我多次提到的我们可以使用我们的设置来做最优决策。我们需要决定哪种排队配置是最优的。有了我们现在拥有的框架我们可以运行模拟来得到这个答案让我们运行多次模拟改变收银员数量和排队顾客数量看看两种配置在多种可能情况下如何比较。让我们编写另一个函数来调用其他函数以比较两种方法defcompare_trials(cashiers,customer_number,num_trials): Compares multiple trials of the two line configurations. Note that each trial is a paired comparison, it passes the same customer input into both lines and compares the KPIs Inputs: cashiers (int) : number of cashiers or cash registers customer_number (int) : number of customers in the system num_trials (int) : number of trials to be run Outputs: single_line_watis (list) : list of wait times for the last customer of each single line trial multi_line_waits (list) : list of wait times for the last customer of each multi line trial single_line_waits[]multi_line_waits[]for_inrange(num_trials):customerslist(np.random.exponential(0.5,customer_number))single_line_waitsingle_line_trial(cashiers,customers)single_line_waits.append(single_line_wait)multi_line_waitmultiple_lines_trial(cashiers,customers)multi_line_waits.append(multi_line_wait)returnsingle_line_waits,multi_line_waits现在代码已经编写完成让我们最终运行模拟并创建一些结果的可视化cashier_based_waits{}# iterate thru different numbers of cashiersforcashierin[2,5,8]:temp_single_wait_times[]temp_multi_wait_times[]# iterate thru different numbers of customersforcust_numinrange(10,150):temp_single_line,temp_multi_linecompare_trials(cashier,cust_num,150)# gather and save KPIs in liststemp_single_meannp.mean(temp_single_line)temp_multi_meannp.mean(temp_multi_line)temp_single_wait_times.append(temp_single_mean)temp_multi_wait_times.append(temp_multi_mean)cashier_based_waits[cashier](temp_single_wait_times,temp_multi_wait_times)这里是制作图表的代码# Create subplotsfig,axesplt.subplots(1,3,figsize(15,5))# 1 row, 3 columnsxrange(10,150)# Plot data on each subplotaxes[0].plot(x,cashier_based_waits[2][0],labelsingle line)axes[0].plot(x,cashier_based_waits[2][1],labelmulti lines)axes[0].legend()axes[0].set_title(2 Cashiers)axes[0].set_xlabel(Num Customers)axes[0].set_ylabel(Wait times)axes[1].plot(x,cashier_based_waits[5][0],labelsingle line)axes[1].plot(x,cashier_based_waits[5][1],labelmulti lines)axes[1].set_title(5 Cashiers)axes[1].set_xlabel(Num Customers)axes[1].set_ylabel(Wait times)axes[1].legend()axes[2].plot(x,cashier_based_waits[8][0],labelsingle line)axes[2].plot(x,cashier_based_waits[8][1],labelmulti lines)axes[2].set_title(8 Cashiers)axes[2].set_xlabel(Num Customers)axes[2].set_ylabel(Wait times)axes[2].legend()# Adjust layoutplt.tight_layout()plt.savefig(wait_times.png)# Show plotplt.show()好的这是一项大量工作我们终于得到了分析的结果让我们来看看https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/f9f962cd63189530d952c931125b6215.png超市排队配置等待时间比较 – 作者这里的启示是什么看起来当有 2 名收银员时两种方法基本上是相同的但随着收银员数量的增加差异开始出现。多行方法开始显示出更多的变异性并且几乎总是高于单行方法。这很合理因为随着收银员数量的增加每条队伍的长度会缩短假设客户数量相同这意味着队伍随机出现更多慢速顾客的可能性更高想想小样本数据集的变异性。由于单行方法只有一条由多个收银台服务的队伍它不受相同“小样本量”现象的影响。鉴于这些结果如果没有需要考虑的外部因素并且我们认为我们的模拟能够代表现实世界我们应该使用单行配置以获得更短和更稳定的等待时间。注意我们不必做出这个决定然后永远不再回头我们可以决定继续使用单行策略然后观察我们商店的实际等待时间看看是否观察到预期的结果。也许我们并不完全相信我们的模拟在推荐单行而不是多行时是正确的。我们可以使用模拟的结果将大多数我们的商店设置为单行配置因为我们认为这是最好的而将较小的一部分设置为多行。然后我们可以观察看看我们的模拟结论是否在现实世界中得以实现。即使我们对自己的模拟并不完全满意我们仍然可以使用我们的学习成果来指导我们在现实世界实验中如何收集更多数据。风险评估使用模拟数据进行的系统建模也可以用来发现系统中的潜在问题、弱点以及瓶颈。在这个领域在我们创建的系统模型之后我们模拟极端数据目的是揭露系统的弱点。如果我们正在对一个航空公司进行系统分析我们可以模拟代表丹佛机场因恶劣天气关闭两天的数据。然后我们可以观察这个极端情况在我们的系统中如何以及在哪里造成严重影响。在杂货店配置的例子中我们可以模拟如果一小部分顾客随机在收银台花费很长时间会发生什么例如使用支票支付为每件商品使用优惠券为日托购买杂货。从我们对系统模型进行模拟和输入极端数据所获得的见解中我们可以更好地理解我们的系统在压力下会在哪里崩溃或紧张。从这个知识中我们可以在系统中放置安全措施或其他规定以防止极端情况。让我们修改我们的模拟数据让 10%的客户结账时间延长 3 倍看看这对我们的等待时间有何影响。在这里我向 compare_trials 函数中添加了 stress_factor 和 stress_pct 参数。这些参数允许用户向随机选择的客户百分比stress_pct添加一个等待乘数stress_factor。下面是更新后的代码defcompare_trials(cashiers,customer_number,num_trials,stress_factor0,stress_pct0.10): Compares multiple trials of the two line configurations. Has the capacity to stress the system by creating customers that have a checkout time that is increased by a multiplier. Note that each trial is a paired comparison, it passes the same customer input into both lines and compares the KPIs. Inputs: cashiers (int) : number of cashiers or cash registers customer_number (int) : number of customers in the system num_trials (int) : number of trials to be run stress_factor (float, def 0) : multiplier to customers wait time if customer is selected as a stress customer stress_pct (float, def 0.1) : percent of customers that are randomly selected to be stress customers Outputs: single_line_watis (list) : list of wait times for the last customer of each single line trial multi_line_waits (list) : list of wait times for the last customer of each multi line trial single_line_waits[]multi_line_waits[]for_inrange(num_trials):customerslist(np.random.exponential(0.5,customer_number))# add stress factorifstress_factor0:num_slow_custsint(customer_number*stress_pct)slow_cust_indexnp.random.choice(range(0,customer_number),num_slow_custs)foriinslow_cust_index:customers[i]customers[i]*stress_factor single_line_waitsingle_line_trial(cashiers,customers)single_line_waits.append(single_line_wait)multi_line_waitmultiple_lines_trial(cashiers,customers)multi_line_waits.append(multi_line_wait)returnsingle_line_waits,multi_line_waits在这个新功能到位后让我们运行一个有 3 名收银员和 100 名客户的有无压力的场景 - 我们将看看压力如何影响我们最喜欢的配置即单行等待时间。single_line_stress,multi_line_stresscompare_trials(3,100,100,stress_factor3,stress_pct0.1)single_line_no_stress,multi_line_no_stresscompare_trials(3,100,100)plt.hist(single_line_stress,labelStressed Scenario,alpha0.5)plt.hist(single_line_no_stress,labelBaseline Scenario,alpha0.5)plt.legend()plt.savefig(stressed_dist.png)plt.show()https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/3ace2ae79c8bf04592cf3a36def3cb6a.png比较无压力与有压力场景 - 作者哇那 10%的慢速客户真的在我们的队伍中造成了问题我们不仅平均等待时间更长而且波动性也更高现在我们可以量化与较慢客户相关的挑战。如果我们觉得压力测试的结果超出了我们的容忍阈值我们可以考虑潜在的缓解策略。也许我们可以安排一个通常在其他区域工作的额外员工但可以在通常不开放的收银台处处理慢速客户。或者也许我们可以找出导致极端缓慢等待时间的原因并培训收银员使用加快流程的技术。主要的收获是我们可以量化特定压力对我们系统的影响并从这种理解中我们可以创建相应的对策来确保我们的系统即使在极端情况下也能在公差范围内运行。总结系统模拟是一种低成本、灵活的方式来获取关于现实世界系统的见解。模拟系统和数据可用于优化系统以提高效率。它们还可以用于对极端场景进行压力测试。确保模拟场景和数据代表现实世界非常重要。如果我们对它们的有效性有信心我们可以立即实施我们创建的见解或者我们可以使用它们来制定测试策略以收集更多关于见解的信心。