Вложенные циклы for с использованием многопроцессорности

У меня есть быстрый вопрос относительно многопроцессорности в python.

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

Очень упрощенный пример моего кода здесь:

import numpy as np
import pickle
import time

a_range = np.arange(14, 18, 0.2)
b_range = np.arange(1000, 5000, 200)
c_range = np.arange(12, 21, .5)

a_position = range(len(a_range))
b_position = range(len(b_range))
c_position = range(len(c_range))

data_grid = np.zeros([len(a_range), len(b_range), len(c_range)])
record_data = []

start_time = time.time()

for (a,apos) in zip(a_range, a_position):
    for (b, bpos) in zip(b_range, b_position):
        for (c, cpos) in zip(c_range, c_position):
            example = a+b+c  #The math in my model is much more complex and takes
            #about 7-8 seconds to process
            data_grid[apos, bpos, cpos] = example
            record_data.append([a, b, c, example])

with open('Test_File', 'wb') as f: 
    pickle.dump(record_data, f) 

np.save('example_values', data_grid) 

print 'Code ran for ', round(time.time()-start_time,2), ' seconds'

Теперь у меня абсолютно нулевой опыт многопроцессорной обработки, поэтому моей первой попыткой было преобразовать циклы for в функцию, а затем вызвать многопроцессорную функцию следующим образом:

def run_model(a, b, c, apos, bpos, cpos):
    example=a+b+c  
    data_grid[apos, bpos, cpos]=example
    record_data.append([a, b, c, example])

from multiprocessing import Pool

if __name__=='__main__':
    pool=Pool(processes=4)
    pool.map(run_model, [a_range, b_range, c_range, a_position, b_positon, c_positon])
    pool.close()
    pool.join()

Однако это не удалось при вызове pool.map. Я понимаю, что эта функция принимает только один повторяемый аргумент, но я не знаю, как решить проблему. Я также сомневаюсь, что переменная data_grid будет заполнена правильно. Результатом, который я хочу получить от этой функции, являются два сохраненных файла: один как массив значений, индексы которых соответствуют значениям a, b и c, а последний представляет собой список списков, содержащих значения a, b, c и результирующее значение (пример в коде выше)

Спасибо за любую помощь!

-Буду


person Will.Evo    schedule 29.06.2016    source источник
comment
Я думаю, numpy.meshgrid поможет, но я не могу попробуйте прямо сейчас. Посмотри на это.   -  person wwii    schedule 30.06.2016
comment
Просто комментарий относительно того, как вы используете пул и карту: я думаю, вам может понадобиться вернуть значения (вместо добавления значений к record_data) из вашей функции run_model и получить результат в переменной, такой как res = p.map(f, [1, 2, 3]). Кроме того, метод map принимает (по крайней мере, в Python 3) аргумент chunksize для разделения вашего iterable.   -  person mgc    schedule 30.06.2016
comment
(Я не видел, чтобы ваш объект data_grid также был глобальной переменной, но вам может понадобиться взглянуть на примитивы синхронизации или общие объекты ctypes части документации для совместного использования переменных между процессами)   -  person mgc    schedule 30.06.2016
comment
Спасибо мгс. Я посмотрю на все это.   -  person Will.Evo    schedule 30.06.2016


Ответы (2)


Это не решает проблему многопроцессорности, но может ускорить процесс.

Ваш шаблон использования вложенных циклов для построения координат nd и последующей работы с ними можно векторизировать с помощью ```numpy.meshgrid````. Не зная ваших фактических расчетов, этот подход не может быть протестирован.

import numpy as np
a = np.array([0,1,2])
b = np.array([10,11,12])
c = np.array([20,21,22])

x, y, z = np.meshgrid(a,b,c)

>>> x
array([[[0, 0, 0],
        [1, 1, 1],
        [2, 2, 2]],

       [[0, 0, 0],
        [1, 1, 1],
        [2, 2, 2]],

       [[0, 0, 0],
        [1, 1, 1],
        [2, 2, 2]]])
>>> y
array([[[10, 10, 10],
        [10, 10, 10],
        [10, 10, 10]],

       [[11, 11, 11],
        [11, 11, 11],
        [11, 11, 11]],

       [[12, 12, 12],
        [12, 12, 12],
        [12, 12, 12]]])
>>> z
array([[[20, 21, 22],
        [20, 21, 22],
        [20, 21, 22]],

       [[20, 21, 22],
        [20, 21, 22],
        [20, 21, 22]],

       [[20, 21, 22],
        [20, 21, 22],
        [20, 21, 22]]])
>>> 



f = x + y + z

>>> f
array([[[30, 31, 32],
        [31, 32, 33],
        [32, 33, 34]],

       [[31, 32, 33],
        [32, 33, 34],
        [33, 34, 35]],

       [[32, 33, 34],
        [33, 34, 35],
        [34, 35, 36]]])
>>> 

Существует также возможность использовать meshgrid для создания фактических точек, а затем использовать один цикл для итерации по точкам - при таком подходе вы теряете пространственную информацию, если не можете понять, как изменить результат. Я нашел это в ответе SO https://stackoverflow.com/a/18253506/2823755

points = np.vstack([x,y,z]).reshape(3, -1).T

>>> points
array([[ 0, 10, 20],
       [ 0, 10, 21],
       [ 0, 10, 22],
       [ 1, 10, 20],
       [ 1, 10, 21],
       [ 1, 10, 22],
       [ 2, 10, 20],
       [ 2, 10, 21],
       [ 2, 10, 22],
       [ 0, 11, 20],
       [ 0, 11, 21],
       [ 0, 11, 22],
       [ 1, 11, 20],
       [ 1, 11, 21],
       [ 1, 11, 22],
       [ 2, 11, 20],
       [ 2, 11, 21],
       [ 2, 11, 22],
       [ 0, 12, 20],
       [ 0, 12, 21],
       [ 0, 12, 22],
       [ 1, 12, 20],
       [ 1, 12, 21],
       [ 1, 12, 22],
       [ 2, 12, 20],
       [ 2, 12, 21],
       [ 2, 12, 22]])
>>>

Вы можете создать функцию и применить ее к points

def g(point):
    x, y, z = point
    return x + y + z

result = np.apply_along_axis(g, 1, points)

>>> result
array([30, 31, 32, 31, 32, 33, 32, 33, 34, 31, 32, 33, 32, 33, 34, 33, 34, 35, 32, 33, 34, 33, 34, 35, 34, 35, 36])
>>>

Изменить форму этого примера просто:

>>> result.reshape(3,3,3)
array([[[30, 31, 32],
        [31, 32, 33],
        [32, 33, 34]],

       [[31, 32, 33],
        [32, 33, 34],
        [33, 34, 35]],

       [[32, 33, 34],
        [33, 34, 35],
        [34, 35, 36]]])
>>> 

Проверьте, чтобы убедиться, что они оба одинаковы

>>> np.all(result.reshape(3,3,3) == f)
True
>>> 

Для более сложной математики просто переберите точки:

result = []
for point in points:
    example = some_maths
    result.append(example)

result = np.array(result).reshape(shape_of_the_3d_data)
person wwii    schedule 30.06.2016
comment
Спасибо за предложения. К сожалению, вычисления не так просты, как a+b+c, и изменить их сейчас было бы очень сложно. Я бы предпочел работать с той структурой, которая у меня есть, но использовать многопроцессорность, чтобы ускорить ее, если это возможно. - person Will.Evo; 30.06.2016
comment
@Will.Evo - так что второй метод, хотя и медленнее первого, все же может быть немного быстрее, чем вложенные циклы Python, и когда вы работаете с многопроцессорной обработкой, вы все равно можете его использовать. Вам не нужно использовать apply_along_axis, просто повторите points — попробуйте на небольшом наборе данных и посмотрите, поможет ли это. - person wwii; 30.06.2016
comment
Я начинаю понимать, что вы говорите... ваши предложения на самом деле привели меня к запуску многопроцессорной обработки. Я вернул список значений из модели, но мне все еще нужно проверить их порядок. Я также застрял в том, чтобы заставить многопроцессорную обработку выполнять определенные части кода, а не весь файл. В любом случае, я работаю над этим (хотя я знаю, что кто-то на этом сайте может сделать это за две секунды, ха-ха) - person Will.Evo; 30.06.2016
comment
@Will.Evo - можете ли вы изменить модель, за исключением чего-то вроде namedtuple в котором есть поле для данных и поле для исходного местоположения? Модель работает с данными field и возвращает namedtuple с координатами, чтобы вы могли восстановить их? - person wwii; 04.07.2016
comment
На самом деле я только что написал метод meshgrid для модели и протестировал его на коротком прогоне. Кажется, работает нормально, но я не узнаю, сколько времени сэкономлено, до сегодняшнего вечера, когда я запущу более длинную итерацию модели. Спасибо за помощь. Я дам вам знать, как это происходит. PS: я отказался от своих стремлений к многопроцессорности. Я нахожусь на стажировке, и у меня осталось всего три недели для моего проекта, я должен сосредоточиться на получении результатов (даже если это означает длительные прогоны моделей). - person Will.Evo; 06.07.2016
comment
Поэтому я протестировал метод meshgrid, и он отлично работает (сравнил его с предыдущим запуском, чтобы убедиться), но сэкономленное время было не таким уж большим. Я бы сказал, что экономлю 5-10% времени работы. Это означает примерно час, сэкономленный для более длинных пробежек, и всего несколько минут для более коротких пробежек. Это чище, так что это приятно. Я собираюсь опубликовать свое решение в качестве ответа в ближайшее время. Спасибо за помощь. - person Will.Evo; 07.07.2016
comment
как ни странно, пример, который я разместил выше, фактически теряет время с методом meshgrid... довольно много времени (20 секунд, когда я сделал большие диапазоны a, b и c). - person Will.Evo; 07.07.2016
comment
@Will.Evo, двадцать секунд на цикл или двадцать дополнительных секунд на четырнадцатичасовой пробежке? 20x20x20 - это 8000 циклов, если фактические вычисления занимают семь секунд, то это 15 часов только для вычислений. Извиняюсь, - person wwii; 07.07.2016
comment
нет, я имею в виду пример кода, который я предоставил в своем первом сообщении. Код работал около 11 секунд с циклами for и около 30 секунд с методом meshgrid. Я сделал массивы диапазонов для a, b и c достаточно большими, чтобы код выполнялся в течение приличного времени для сравнения. - person Will.Evo; 07.07.2016

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

import numpy as np
import time

a_range = np.arange(14, 18, 1)
b_range = np.arange(1000, 2200, 200)
c_range = np.arange(12, 21, 1)

a_position = range(len(a_range))
b_position = range(len(b_range))
c_position = range(len(c_range))

mesha, meshb, meshc = np.meshgrid(a_range, b_range, c_range)
mesh_vals = np.vstack([mesha, meshb, meshc]).reshape(3, -1).T

mesha_pos, meshb_pos, meshc_pos = np.meshgrid(a_position, b_position, c_position)
mesh_positions = np.vstack([mesha_pos, meshb_pos, meshc_pos]).reshape(3,-1).T

data_grid = np.zeros([len(a_range), len(b_range), len(c_range)])
record_data = []

start_time = time.time()

for pol in range(len(mesh_positions)):
    example = mesh_vals[pol][0]+ mesh_vals[pol][1]+ mesh_vals[pol][2]
    data_grid[mesh_positions[pol][0], mesh_positions[pol][1], mesh_positions[pol][2]] = example
    record_data.append([mesh_vals[pol][0], mesh_vals[pol][1], mesh_vals[pol][2], example])

print 'Code ran for ', round(time.time()-start_time,2), ' seconds'

Это на самом деле, после дальнейшего исследования, привело к довольно значительному увеличению времени работы. Разница между циклами for и этим методом составила 20 секунд при большом диапазоне значений a, b и c. Я понятия не имею, почему, но я знаю, что такая конструкция задачи должна упростить многопроцессорную обработку, поскольку приходится иметь дело только с одним циклом for.

person Will.Evo    schedule 07.07.2016