在Python中使用OpenCL进行并行循环

时间:2018-08-04 21:16:50

标签: python parallel-processing opencl gpu pyopencl

我在矩阵y中有一个给定的数据集,我想用它来训练不同的SOM。 SOM是一维的(一条线),其神经元数量有所不同。我首先训练了大小为N=2的SOM,最后训练了N=NMax的SOM,总共得到了NMax-2+1个SOM。对于每个SOM,我想在训练结束后存储权重,然后再继续下一个SOM。

这里使用PyOpenCL的全部要点是,每个外部循环都独立于其他循环。即,对于N的每个值,脚本都不关心N接受其他值时会发生什么。通过手动更改NMax-2+1的值,运行脚本N可能会得到相同的结果。

考虑到这一点,我希望能够使用GPU同时执行这些独立迭代中的每一个,从而使所花费的时间大大减少。但是,速度的提高将小于1/(NMax-2+1),因为每次迭代比以前的迭代更昂贵,对于更大的N,则需要进行更多的计算。

是否可以“翻译”此代码以在GPU上运行?我以前从未使用过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)

1 个答案:

答案 0 :(得分:1)

我正在尝试给出一个完整的答案。

首先:

是否可以使用(py)OpenCL将该代码修改为在GPU上运行?

大概是。

可以自动完成吗?

不(afaik)。

我对OpenCL的大多数疑问都是:“是否值得将这段代码移植到OpenCL以获得加速收益?”您要说的是,您的外循环独立于其他运行的结果,这使代码基本上可以并行化。在一个简单的实现中,每个OpenCL工作元素将使用略有不同的输入参数执行相同的代码。不考虑主机与设备之间的数据传输开销,此方法的运行时间将等于最慢迭代的运行时间。根据外部循环中的迭代,这可能会带来巨大的速度提升。只要数量保持相对较小,就可以尝试使用python中的multiprocessing模块在​​CPU而非GPU上并行化这些迭代。

如果要并行运行大量进程(大约1000个或更多),则通常只有移植到GPU才有意义。因此,在您的情况下,如果您真的想大幅提高速度,请查看是否可以在循环中 并行化所有计算。例如,您有150次迭代和2000个数据点。如果您能够以某种方式并行化这2000个数据点,则可以提供更大的速度提升,这可以证明将整个代码移植到OpenCL的工作。

TL; DR: 首先尝试在CPU上并行化。如果您发现需要同时运行数百个以上的进程,请转到GPU。

更新:用于使用多处理(无回调)在CPU上并行化的简单代码

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)