【发布时间】:2020-02-17 09:21:13
【问题描述】:
我正在尝试为我的地质研究创建某种简化的 Oracle Crystal Ball 应用程序,它将使用 P90(90% 置信度)和 P10(10% 置信度)值作为不同概率场景的输入和返回分布。听起来像蒙特卡洛分布。我是 Python 新手,最近才开始,顺便说一句 :)
本主题将分为四个关键部分:
- 作品范围的一般描述。
- 伪编码(不过以前从未尝试过)。
- 实际 Python 代码。
- 我在这里的原因或逻辑/代码问题。
PART 1. 作品范围概述。
-
为简单起见,假设我们只有三个类别,每个类别都有 P90 和 P10 参数,它们之间没有任何步骤:
- cat_1:[1, 2]
- cat_2: [2, 4]
- cat_3:[3, 6]
-
利用笛卡尔积,我们得到以下 8 个可能场景的列表:
- [1, 2, 3], [1, 2, 6], [1, 4, 3], [1, 4, 6], [2, 2, 3], [2, 2, 6] , [2, 4, 3], [2, 4, 6]
-
将每个列表中的参数相乘得到以下产品:
- [6、12、12、24、12、24、24、48]
-
测量每个产品的频率会导致:
{6: 1, 12: 3, 24: 3, 48: 1},或考虑百分比:
{6: 12.5%, 12: 37.5%, 24: 37.5%, 48: 12:5%,}表示出现12或24的概率高于6或48。
这就是我想要得到的结果:知道产品能够获得均值、中值和众数值的概率。
-
对于我的硬件来说,困难的部分是真实案例中可能出现的大量场景。共有六个类别,在 P90 和 P10 值之间有小步长。考虑公制,P90和P10的取值范围可能如下:
- 正方形面积:0.01 - 100.00 km2,步长0.01;
- 层厚:0.10 - 100.00 m,步长0.1;
- 孔隙率:0.01 - 1.00 p.u.,步长 0.01;
- 饱和度:0.01 - 1.00 p.u.,步长 0.01;
- 压力:1 - 2000 atm,步骤 1 atm;
- 表面:0.01 - 1.00 p.u.,步长 0.01。
通常,实际案例研究将使用更窄的范围,例如 0.1 - 2.0 km2 的面积,1 - 10 m 的厚度,8 - 15 的孔隙度等。尽管如此,即使在这种情况下,它听起来也像考虑到上述步骤,“谷歌”可能的场景数量。结果,我收到以下通知,这是关键问题:
进程以退出代码 137 结束(被信号 9:SIGKILL 中断)。
当总计算量超过 ~10MM 和 ~1 分钟时会发生这种情况(经过实验检查,因此数字是粗略的)。
第 2 部分。伪编码。
良好的做法是在伪编码时不应该进行抽象,但是我在这个领域的经验为零,因此会尽力而为。
User inputs minimum possible values (P90) for total 6 categories
User inputs maximum possible values (P10) for total 6 categories
Total 6 list are created (square area, layer thickness, porosity etc.), 1 per each category that contain a range of possible values and indicated step (P90_category1, P10_category1, step1)
Use a Cartesian product to create a list_of_tuples with possible scenarios
Convert list_of_tuples to the list_of_lists
Create empty_list
for each element in the list_of_lists:
calculate its product
append to the empty_list
Round values in the empty_list
Create a dictionary that counts similar values in the empty_list
Calculate a probability of each value according to its repetition frequency in the dictionary
就是这样。还应用了一些基本的统计数据和绘图,但这不是关键时刻。
第 3 部分。实际 Python 代码。
最初的 P90 值(90% 置信度):
P90_area = float(input('P90 area: '))
P90_thickness = float(input('P90 thickness: '))
P90_porosity = float(input('P90 porosity: '))
P90_saturation = float(input('P90 saturation: '))
P90_pressure = float(input('P90 pressure: '))
P90_surface = float(input('P90 surface: '))
然后是 P10 值(10% 置信度):
P10_area = float(input('P10 area: '))
P10_thickness = float(input('P10 thickness: '))
P10_porosity = float(input('P10 porosity: '))
P10_saturation = float(input('P10 saturation: '))
P10_pressure = float(input('P10 pressure: '))
P10_surface = float(input('P10 surface: '))
使用特定步骤创建从 P90 到 P10 的值范围
area_values = np.arange(P90_area, P10_area + 0.01, 0.01)
thickness_values = np.arange(P90_thickness, P10_thickness + 0.1, 0.1)
porosity_values = np.arange(P90_porosity, P10_porosity + 0.01, 0.01)
saturation_range = np.arange(P90_saturation, P10_saturation + 0.01, 0.01)
pressure_range = np.arange(P90_pressure, P10_pressure + 1, 1)
surface_range = np.arange(P90_surface, P10_surface + 0.01, 0.01)
将所有列表组合成笛卡尔积(即[(area1,thickness1,porosity1),(area1,thickness1,porosity2)等]):
list_of_tuples = list(itertools.product(area_values, thickness_values, porosity_values, saturation_range, pressure_range, surface_range)
将元组列表转换为列表列表:
list_of_lists = [list(elem) for elem in list_of_tuples]
创建一个包含相乘值的列表并对它们进行排序('np.prod' 为每个列表返回一个产品):
multiplied_values = []
for i in list_of_lists:
i = np.prod(np.array(i))
multiplied_values.append(i)
multiplied_values = sorted(multiplied_values)
四舍五入:
rounded_values = [float(Decimal('%.2f' % elem)) for elem in multiplied_values]
创建一个统计所有相似/独特对象的字典:
counts = Counter(rounded_values)
通过将值除以列表中元素的总数来计算概率:
probability_mass = {k: v/total for k, v in counts.items()}
它有效,这里有简单的统计数据和特定案例的图表:
- 总计算量:4899510
- P90 为:5.60
- P10 是:43.41
- P50(概率最大的值)为:15.24
- 平均值为:23.80
Figure. Probability distribution diagram
第一个问题很关键,因为它阻塞了大堆数据的计算:
第 4 部分。关键问题。
第一季度。关键问题:
因此,我收到以下通知,这是关键问题:
进程以退出代码 137 结束(被信号 9:SIGKILL 中断)。
根据类似的主题,我的脚本很可能由于 CPU 使用率过高而被操作系统杀死。我在运行代码时使用 'top' 命令检查了 CPU 负载,当它可以处理输入参数时,CPU 负载高达 100%,在某些时候被中断时高达 110%。
规格:笔记本电脑 Asus G531GU | i7-9750H CPU 2.60GHz | GeForce GTX 1660 TI,6Gb | 16Gb DDR4 | Ubuntu 18 | PyCharm 社区 IDE。
问题:无论如何,我怎样才能摆脱这种中断,让脚本运行到需要的时间?我很乐意等待为大型数据堆栈获得正确的分布。为每个参数增加一个步骤是一个硬核选项,我不想这样做。
第二季度。概率分布图看起来不像经典的正态分布,而最大概率值和平均值之间的差异很大。你怎么看,代码的逻辑会不会有问题?
附:我知道这个脚本看起来很颠簸,希望你的眼睛不会流血)
【问题讨论】:
-
听起来您正在实施一种需要计算所有可能性的方法。没关系,这是一个很好的开始方式。在这一点上,我的建议是首先通过增加每个变量的步长来减少需要计算的值的数量。目前该步长似乎是 0.01,也许可以尝试 0.1。这个想法是让它使用更少数量的值,然后尝试减小步长(增加步数)。此外,一旦你对问题有了感觉,就寻找一种更具分析性或象征性的方法。祝你好运,玩得开心。
-
@RobertDodier 谢谢你,罗伯特)正确,关键是计算每一个可能的场景。当我们确实有一系列可能的地质条件值(提到的面积、厚度等)时,这在地质学中被广泛使用,但是不知道精确值,因为目前还没有直接测量。所以我做了你写的,从小开始,不断增加参数和减少步骤。这就是我在实际情况中将面临的问题,这就是为什么操作系统的中断非常令人沮丧,因为脚本可以工作,但范围相对较小)
-
我会查看完成计算所需的时间如何随步数的增加而变化,并尝试估计使用我最初想要的步数完成计算所需的时间。如果结果太多,有一些策略可以尝试保持在时间和空间的限制范围内。例如。用大步搜索到附近,然后小步细化。或应用启发式搜索方法,如模拟退火,或无梯度搜索,如多面体(变形虫)算法,或在存在梯度时使用梯度的方法(LBFGS 等)。
-
@RobertDodier 这么多术语我不熟悉,但这看起来是一个进一步了解它的好机会)谢谢你的提示和指导!如果我有任何新的正面或负面结果,我会在这里发表评论。
标签: python statistics cpu probability montecarlo