Распараллелить циклы, используя OpenCL в Python

1

У меня есть заданный набор данных в матрице y и я хочу обучать с ним разные ЗВОЛ. ЗВОЛ одномерна (линия), и число нейронов меняется. Сначала я тренирую ЗВОЛ размером N=2, и N=NMax, давая в общей сложности NMax-2+1. Для каждого SOM я хочу сохранить весы после завершения обучения, прежде чем переходить к следующему SOM.

Весь смысл использования PyOpenCL здесь состоит в том, что каждая из внешних петель не зависит от других. А именно, для каждого значения N скрипт не заботится о том, что происходит, когда N принимает другие значения. Можно было бы получить тот же результат, что и скрипт NMax-2+1 раз, изменяя значение N вручную.

Имея это в виду, я надеялся, что смогу выполнить каждую из этих независимых итераций одновременно с использованием графического процессора, так что затраченное время значительно сократится. Увеличение скорости будет меньше 1/(NMax-2+1) хотя, поскольку каждая итерация дороже, чем предыдущие, как и для больших значений N, выполняются больше вычислений.

Есть ли способ "перевести" этот код для работы на графическом процессоре? Я никогда раньше не использовал OpenCL, поэтому дайте мне знать, слишком ли это или глупо, так что я могу задать более конкретный вопрос. Код является самодостаточным, поэтому не стесняйтесь попробовать его. Четыре константы, объявленные в начале, могут быть изменены на все, что вам нравится (учитывая, что NMax > 1 и все остальные являются строго положительными).

import numpy as np
import time

m = 3 # Dimension of datapoints
num_points = 2000 # Number of datapoints
iterMax = 150 # Maximum number of iterations
NMax = 3 # Maximum number of neurons
#%%
np.random.seed(0)
y = np.random.rand(num_points,m) # Generate always the same dataset
sigma_0 = 5 # Initial value of width of the neighborhood function
eta_0 = 1 # Initial value of learning rate
w = list(range(NMax - 1))
wClusters = np.zeros((np.size(y,axis = 0),NMax - 1)) # Clusters for each N

t_begin = time.clock() # Start time
for N in range(NMax-1): # Number of neurons for this iteration
    w[N] = np.random.uniform(0,1,(N+2,np.size(y,axis=1))) - 0.5 # Initialize weights
    iterCount = 1
    while iterCount < iterMax:
        # Mix up the input patterns
        mixInputs = y[np.random.permutation(np.size(y,axis = 0)),:]
        # Sigma reduction
        sigma = sigma_0 - (sigma_0/(iterMax + 1)) * iterCount
        s2 = 2*sigma**2
        # Learning rate reduction
        eta = eta_0 - (eta_0/(iterMax + 1)) * iterCount
        for selectedInput in mixInputs: # Pick up one pattern
            # Search winning neuron
            aux = np.sum((selectedInput - w[N])**2, axis = -1)
            ii = np.argmin(aux) # Neuron 'ii' is the winner
            jjs = abs(ii - list(range(N+2)))
            dists = np.min(np.vstack([jjs , abs(jjs-(N+2))]), axis = 0)
            # Update weights
            w[N] = w[N] + eta * np.exp((-dists**2)/s2).T[:,np.newaxis] * (selectedInput - w[N])
        print(N+2,iterCount)
        iterCount += 1    
    # Assign each datapoint to its nearest neuron
    for kk in range(np.size(y,axis = 0)):
        aux = np.sum((y[kk,] - w[N])**2,axis=-1)
        ii = np.argmin(aux) # Neuron 'ii' is the winner
        wClusters[kk,N] = ii + 1
t_end = time.clock() # End time
#%%
print(t_end - t_begin)
Теги:
parallel-processing
gpu
opencl
pyopencl

1 ответ

1
Лучший ответ

Я пытаюсь дать несколько полный ответ.

Прежде всего:

Можно ли настроить этот код для запуска на графическом процессоре с помощью (py) OpenCL?

Скорее всего, да.

Это можно сделать автоматически?

Нет (афайк).

Большинство вопросов, которые я рассказываю о OpenCL, следующие: "Стоит ли переносить этот кусок кода в OpenCL для ускорения?" Вы заявляете, что ваш внешний цикл независим от результатов других прогонов, что делает код в основном параллелизуемым. В простой реализации каждый рабочий элемент OpenCL выполнял бы тот же код с несколько разными входными параметрами. Не учитывая накладные расходы при передаче данных между хостом и устройством, время работы этого подхода будет равняться времени работы самой медленной итерации. В зависимости от итераций в вашем внешнем цикле это может быть огромным увеличением скорости. Пока номера остаются относительно небольшими, вы можете попробовать использовать модуль multiprocessing в python для параллелизации этих итераций на CPU вместо графического процессора.

Портирование на графический процессор обычно имеет смысл только в том случае, если параллельно нужно запустить огромное количество процессов (около 1000 или более). Поэтому в вашем случае, если вы действительно хотите увеличить скорость, посмотрите, можете ли вы распараллелить все вычисления внутри цикла. Например, у вас есть 150 итераций и 2000 точек данных. Если бы вы могли как-то распараллелить эти 2000 точек данных, это могло бы обеспечить гораздо больший прирост скорости, что могло бы оправдать работу по переносу всего кода на OpenCL.

TL; DR: сначала попробуйте распараллелить CPU. Если вы обнаружите необходимость запуска более нескольких 100 процессов одновременно, перейдите на GPU.

Обновление: простой код для распараллеливания по процессору с использованием многопроцессорности (без обратного вызова)

import numpy as np
import time
import multiprocessing as mp

m = 3 # Dimension of datapoints
num_points = 2000 # Number of datapoints
iterMax = 150 # Maximum number of iterations
NMax = 10 # Maximum number of neurons
#%%
np.random.seed(0)
y = np.random.rand(num_points,m) # Generate always the same dataset
sigma_0 = 5 # Initial value of width of the neighborhood function
eta_0 = 1 # Initial value of learning rate
w = list(range(NMax - 1))
wClusters = np.zeros((np.size(y,axis = 0),NMax - 1)) # Clusters for each N

def neuron_run(N):
    w[N] = np.random.uniform(0,1,(N+2,np.size(y,axis=1))) - 0.5 # Initialize weights
    iterCount = 1
    while iterCount < iterMax:
        # Mix up the input patterns
        mixInputs = y[np.random.permutation(np.size(y,axis = 0)),:]
        # Sigma reduction
        sigma = sigma_0 - (sigma_0/(iterMax + 1)) * iterCount
        s2 = 2*sigma**2
        # Learning rate reduction
        eta = eta_0 - (eta_0/(iterMax + 1)) * iterCount
        for selectedInput in mixInputs: # Pick up one pattern
            # Search winning neuron
            aux = np.sum((selectedInput - w[N])**2, axis = -1)
            ii = np.argmin(aux) # Neuron 'ii' is the winner
            jjs = abs(ii - list(range(N+2)))
            dists = np.min(np.vstack([jjs , abs(jjs-(N+2))]), axis = 0)
            # Update weights
            w[N] = w[N] + eta * np.exp((-dists**2)/s2).T[:,np.newaxis] * (selectedInput - w[N])
        print(N+2,iterCount)
        iterCount += 1    
    # Assign each datapoint to its nearest neuron
    for kk in range(np.size(y,axis = 0)):
        aux = np.sum((y[kk,] - w[N])**2,axis=-1)
        ii = np.argmin(aux) # Neuron 'ii' is the winner
        wClusters[kk,N] = ii + 1

t_begin = time.clock() # Start time   
#%%

def apply_async():
    pool = mp.Pool(processes=NMax)
    for N in range(NMax-1):
        pool.apply_async(neuron_run, args = (N,))
    pool.close()
    pool.join()
    print "Multiprocessing done!"

if __name__ == '__main__':
    apply_async()

t_end = time.clock() # End time 
print(t_end - t_begin)
  • 0
    Распараллеливание на ЦП с использованием multiprocessing было даже медленнее, чем не распараллеливание. Имеет ли это смысл?
  • 0
    В принципе, это не должно быть так. В некоторых случаях узким местом является планирование и накладные расходы (запуска потоков / задач / процессов), а также управление памятью, а параллельная программа работает медленнее. Можете ли вы подтвердить (в вашем диспетчере задач), что параллельная программа работает на всех ядрах? Если используется только одно ядро, это может быть проблемой. Если вам нужна дополнительная информация, вы также можете отправить мне письмо по электронной почте, чтобы решить вашу конкретную проблему, на которую, мне кажется, сложно ответить в рамках этого вопроса.
Показать ещё 9 комментариев

Ещё вопросы

Сообщество Overcoder
Наверх
Меню