Распределение вероятностей приводит к «процессу, завершенному с кодом выхода 137 (прерван сигналом 9: SIGKILL)» - PullRequest
0 голосов
/ 21 октября 2019

Я пытаюсь создать какое-то упрощенное приложение Oracle Crystal Ball для своих геологических исследований, которое будет использовать значения P90 (90% достоверности) и P10 (10% достоверности) в качестве входных данных и обратное распределение различных вероятностных сценариев. Похоже на распределение Монте-Карло. Я новичок в Python, только недавно начал, кстати:)

Эта тема будет разделена на четыре ключевые части:

  1. Общее описание объема работ.
  2. Псевдокодирование (хотя никогда раньше не пытался).
  3. Фактический код Python.
  4. Причина, по которой я здесь, или проблемы с логикой / кодом.

ЧАСТЬ 1. Общее описание объема работ.

  1. Для простоты предположим, что у нас есть только три категории, каждая с параметрами P90 и P10 без каких-либо шагов междуих:

    • cat_1: [1, 2]
    • cat_2: [2, 4]
    • cat_3: [3, 6]
  2. Используя декартово произведение, мы получаем следующие 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]
  3. Умножение параметров в каждом списке приводит к следующим продуктам:

    • [6, 12, 12,24, 12, 24, 24, 48]
  4. Измерение частоты каждого продукта приводит к:

    • {6: 1, 12: 3, 24: 3, 48: 1} или с учетом процентов:

    • {6: 12,5%, 12: 37,5%, 24: 37,5%, 48: 12: 5%,}, что означает, что вероятность появления 12 или 24 выше, чем 6 или 48.

  5. Вот результат, который я хотел бы получить: знаявероятность того, что продукты смогут получить средние, срединные и модовые значения.

  6. Трудная часть для моего оборудования - это огромное количество возможных сценариев в реальном случае. Всего имеется шесть категорий с небольшими шагами между значениями P90 и P10. С учетом метрической системы диапазон значений P90 и P10 может быть следующим:

    • площадь квадрата: 0,01 - 100,00 км2, шаг 0,01;
    • толщина слоя: 0,10 - 100,00 м,шаг 0,1;
    • пористость: 0,01-1,00 пу, шаг 0,01;
    • насыщение: 0,01-1,00 пу, шаг 0,01;
    • давление: 1 - 2000 атм, шаг 1атм;
    • поверхность: 0,01 - 1,00 пу, шаг 0,01.
  7. Обычно в реальных примерах используются гораздо более узкие диапазоны, скажем, 0,1 - 2,0 км2 дляплощадь квадратного сечения, 1 - 10 м для толщины, 8 - 15 для пористости и т. д. Тем не менее, даже в этом случае это звучит как количество возможных сценариев "google" с учетом упомянутых шагов. В результате я получаю следующее уведомление, которое является ключевой проблемой:

Процесс завершен с кодом выхода 137 (прерван сигналом 9: SIGKILL).

Это происходит, когда общий объем вычислений превышает ~ 10 мм и ~ 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, толщина1, пористость1), (area1, толщина1, пористость2) и т. Д.]):

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

Рис. Диаграмма распределения вероятностей

Первый вопрос имеет решающее значение, поскольку он блокирует вычисление большого стека данных:

ЧАСТЬ 4. Ключевые проблемы.

Q1. Ключевая проблема:

В результате я получаю следующее уведомление, которое является ключевой проблемой:

Процесс завершен с кодом выхода 137 (прерывается сигналом 9: SIGKILL).

По схожим темам, скорее всего, мой скрипт был убит ОС из-за чрезмерной загрузки процессора. Я проверил загрузку ЦП с помощью команды 'top' во время выполнения кода, и ЦП был загружен до 100%, когда он мог обрабатывать входные параметры, и в некоторых моментах до 110% при прерывании.

Характеристики: ноутбук Asus G531GU |i7-9750H CPU 2.60GHz |GeForce GTX 1660 TI, 6 Гб |16 Гб DDR4 |Ubuntu 18 |IDE сообщества PyCharm.

Вопрос: В любом случае, как я могу избавиться от этого прерывания и позволить скрипту работать столько, сколько необходимо? Я умею ждать столько, сколько нужно, чтобы получить правильное распределение для большого стека данных. Увеличение шага для каждого параметра является жесткой опцией, я бы не стал этого делать.

Q2 . Диаграмма распределения вероятностей не похожа на классическое нормальное распределение, тогда как разница между максимально вероятными и средними значениями значительна. Как вы думаете, могут ли быть какие-либо проблемы с логикой кода?

PS Я понимаю, что этот скрипт выглядит довольно ухабистым, надеюсь, ваши глаза не будут кровоточить)

Ответы [ 2 ]

0 голосов
/ 09 ноября 2019

Итак, я сделал то, что вам нужно, в отношении равномерного распределения входных параметров, случайной выборки и декартовых произведений. Результат выглядит как экспоненциальное распределение. Он лучше моделируется распределением Вейбулла.

Я провел некоторый дальнейший анализ, поскольку результаты любого моделирования всегда следует дополнительно изучать, чтобы проверить, достаточно ли моделирования. Чтобы сделать это, я сделал образец Монте-Карло из 10 100 000 000 100 000 100 000 100 000, чтобы получить гистограмму. По сходимости по альфа и бета из подобранного вейбулла мы видим, что достаточно 1 миллиона образцов.

Я уверен, что у вас возникнут вопросы по этому поводу, поэтому, пожалуйста, задавайте их ниже. Обратите внимание, что графики гистограммы представлены в масштабе log-log, поэтому вам необходимо помнить об этом при визуализации распределения (или закомментировать строки xscale и yscale).

Вот результаты: https://i.stack.imgur.com/viQ9i.png https://i.stack.imgur.com/0kc4n.png

А вот код с сгенерированным выводом:

import numpy as np
from tqdm import tqdm
import random
import matplotlib.pyplot as plt
import scipy.stats as ss

#these should be user inputs
area_min = 0.01
area_max = 100
thickness_min = 0.1
thickness_max = 100
porosity_min = 0.01
porosity_max = 1
saturation_min = 0.01
saturation_max = 1
pressure_min = 1
pressure_max = 2000
surface_min = 0.01
surface_max = 1

grid_resolution = 1000 #how finely we will slice each property. I have kept this consistent as it makes more sense to do so when sampling
#With a grid_resolution of 1000, the number of possible combinations here is 1000^6 ==> 10^18 so we will randomly sample the array
#I assume you want to get a probability distribution of these combinations.
area_array = np.linspace(area_min,area_max,grid_resolution)
thickness_array = np.linspace(thickness_min,thickness_max,grid_resolution)
porosity_array = np.linspace(porosity_min,porosity_max,grid_resolution)
saturation_array = np.linspace(saturation_min,saturation_max,grid_resolution)
pressure_array = np.linspace(pressure_min,pressure_max,grid_resolution)
surface_array = np.linspace(surface_min,surface_max,grid_resolution)

#it is important to try different sample sizes to be sure your sample is large enough
samples_to_test = [1,2,3,4,5,6] #log10 scale

xmax = 10**8
alpha_array = []
beta_array = []
plt.figure(figsize=(12,10))
for i,s in enumerate(samples_to_test):
    plt.subplot(231+i)
    samples = 10**s
    product_array = []
    for _ in tqdm(range(samples)):
        area = random.choice(area_array)
        thickness = random.choice(thickness_array)
        porosity = random.choice(porosity_array)
        saturation = random.choice(saturation_array)
        pressure = random.choice(pressure_array)
        surface = random.choice(surface_array)
        product_array.append(area*thickness*porosity*saturation*pressure*surface)

    xvals = np.logspace(1,np.log10(xmax),1000)
    [beta,_,alpha] = ss.weibull_min.fit(data=product_array,floc=0)
    alpha_array.append(alpha)
    beta_array.append(beta)
    weibull_yvals = ss.weibull_min.pdf(xvals,beta,scale=alpha)
    plt.plot(xvals,weibull_yvals)
    print('Weibull fit parameters:\nalpha =',alpha,'\nbeta =',beta)
    [mean,variance] = ss.weibull_min.stats(beta, loc=0, scale=alpha, moments='mv')
    median = ss.weibull_min.median(beta, loc=0, scale=alpha)
    print('Mean =',mean)
    print('Median =',median)
    print('Standard deviation =',variance**0.5)

    plt.hist(product_array,bins=1000,density=True)
    plt.yscale('log')
    plt.xscale('log')
    plt.xlabel('Cartesian Product of parameters')
    plt.ylabel('Probability density ($log_{10}$ scale)')
    plt.title(str('Monte Carlo samples = '+str(samples)))
    plt.xlim(10,xmax)
    plt.ylim(10**-8,0.0001)

plt.suptitle('Probability of of a given cartesian product of the specified parameters\nmeasured using different numbers of Monte Carlo samples')
plt.figure(figsize=(12,5))
plt.subplot(121)
plt.semilogx(10**np.array(samples_to_test),alpha_array,label='alpha')
plt.legend()
plt.subplot(122)
plt.semilogx(10**np.array(samples_to_test),beta_array,label='beta')
plt.legend()
plt.suptitle('Test results for alpha and beta')
plt.show()

Output:
100%|██████████| 10/10 [00:00<?, ?it/s]
Weibull fit parameters:
alpha = 86642.0194345818 
beta = 0.4938259951069627
Mean = 177350.7081149186
Median = 41247.66458603765
Standard deviation = 403557.41514732403
100%|██████████| 100/100 [00:00<00:00, 100246.27it/s]
Weibull fit parameters:
alpha = 177861.91287733015 
beta = 0.6310314479279571
Mean = 251385.7124440623
Median = 99503.40459313976
Standard deviation = 415414.97618995525
100%|██████████| 1000/1000 [00:00<00:00, 199131.37it/s]
Weibull fit parameters:
alpha = 171932.22877129668 
beta = 0.5452693527437176
Mean = 296661.14084923535
Median = 87788.61401806296
Standard deviation = 589615.4680695855
100%|██████████| 10000/10000 [00:00<00:00, 179051.70it/s]
Weibull fit parameters:
alpha = 166909.86147776648 
beta = 0.5172460791589029
Mean = 314175.4976503747
Median = 82176.44526800542
Standard deviation = 670314.3944630618
100%|██████████| 100000/100000 [00:00<00:00, 144477.93it/s]
Weibull fit parameters:
alpha = 167711.26073670806 
beta = 0.5194333533253157
Mean = 313393.61873437575
Median = 82817.74728224205
Standard deviation = 664803.5086740599
100%|██████████| 1000000/1000000 [00:07<00:00, 140706.15it/s]
Weibull fit parameters:
alpha = 168089.6178189406 
beta = 0.5186379527889259
Mean = 314930.2501968761
Median = 82914.8108556469
Standard deviation = 669461.6904337168
0 голосов
/ 30 октября 2019

Поскольку вы пытаетесь рассчитать каждый возможный сценарий, требуемые здесь вычисления возрастают экспоненциально с количеством элементов в каждом из ваших диапазонов. Я хотел бы попробовать отладить полный код для вас, но мне нужны входные данные, так что вы можете опубликовать полный код с уже указанными входными данными, чтобы мы знали, какие разумные значения использовать.

Для немного другогообратите внимание, вместо того, чтобы пытаться исправить ваш код, мы можем начать с попытки решить вашу первоначальную проблему? Когда вы сказали «упрощенный калькулятор распределения вероятностей», что вы имеете в виду? Можете ли вы написать шаги в псевдо-коде, чтобы мы могли понять процесс, прежде чем мы попытаемся понять, как реализовать этот процесс в Python.

В зависимости от вашего ответа на вышесказанное, я мог бы предложить вам использовать метод выборкивместо того, чтобы оценивать каждую возможность. Поиск Монте-Карло симуляции. Если у вас есть предыдущий дистрибутив, который вы обновляете новыми данными, и вы хотите знать апостериорный (окончательный) дистрибутив, рассмотрите возможность использования байесовских методов, в частности, Winbugs (автономная программа, которая не является Python, но идеально подходит для байесовского материала).

PS. Я знаю, что мой ответ, вероятно, более уместно написать в качестве комментария, но, очевидно, вам нужно +50 репутации для этого, и я еще не там: (

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...