我曾经被说服放弃舒适的Matlab编程并开始使用Julia编程。我长期以来一直在研究神经网络,而我认为,现在有了Julia,通过并行计算梯度,我可以更快地完成任务。
梯度不需要一次性在整个数据集上计算;相反,我们可以将计算分割。例如,通过将数据集分成几部分,我们可以在每个部分上计算局部梯度。然后通过相加这些局部梯度来计算总的梯度。
虽然原则很简单,但当我使用Julia并行化时,会出现性能下降,即一个进程比两个进程更快!我显然做错了什么......我已经参考了论坛中其他人提出的问题,但我仍然无法得到完整的答案。我认为我的问题在于有大量不必要的数据移动,但我无法正确修复它。
为避免发布混乱的神经网络代码,我在下面发布一个更简单的示例,它在线性回归设置中复制了我的问题。
下面的代码块创建了一些用于线性回归问题的数据。代码解释了常量,但X是包含数据输入的矩阵。我们随机创建一个权重向量w,当与X相乘时,可以得到一些目标Y。
######################################
## CREATE LINEAR REGRESSION PROBLEM ##
######################################
# This code implements a simple linear regression problem
MAXITER = 100 # number of iterations for simple gradient descent
N = 10000 # number of data items
D = 50 # dimension of data items
X = randn(N, D) # create random matrix of data, data items appear row-wise
Wtrue = randn(D,1) # create arbitrary weight matrix to generate targets
Y = X*Wtrue # generate targets
下面的代码块定义了用于测量回归适应性(即负对数似然)和权重向量梯度的函数:w:
####################################
## DEFINE FUNCTIONS ##
####################################
@everywhere begin
#-------------------------------------------------------------------
function negative_loglikelihood(Y,X,W)
#-------------------------------------------------------------------
# number of data items
N = size(X,1)
# accumulate here log-likelihood
ll = 0
for nn=1:N
ll = ll - 0.5*sum((Y[nn,:] - X[nn,:]*W).^2)
end
return ll
end
#-------------------------------------------------------------------
function negative_loglikelihood_grad(Y,X,W, first_index,last_index)
#-------------------------------------------------------------------
# number of data items
N = size(X,1)
# accumulate here gradient contributions by each data item
grad = zeros(similar(W))
for nn=first_index:last_index
grad = grad + X[nn,:]' * (Y[nn,:] - X[nn,:]*W)
end
return grad
end
end
请注意,上述函数故意没有向量化!我选择不进行向量化,因为最终的代码(神经网络案例)也不会接受任何向量化(我们不要进一步讨论这个问题)。
最后,下面的代码块显示了一个非常简单的梯度下降,试图从给定的数据Y和X中恢复参数权重向量w:
####################################
## SOLVE LINEAR REGRESSION ##
####################################
# start from random initial solution
W = randn(D,1)
# learning rate, set here to some arbitrary small constant
eta = 0.000001
# the following for-loop implements simple gradient descent
for iter=1:MAXITER
# get gradient
ref_array = Array(RemoteRef, nworkers())
# let each worker process part of matrix X
for index=1:length(workers())
# first index of subset of X that worker should work on
first_index = (index-1)*int(ceil(N/nworkers())) + 1
# last index of subset of X that worker should work on
last_index = min((index)*(int(ceil(N/nworkers()))), N)
ref_array[index] = @spawn negative_loglikelihood_grad(Y,X,W, first_index,last_index)
end
# gather the gradients calculated on parts of matrix X
grad = zeros(similar(W))
for index=1:length(workers())
grad = grad + fetch(ref_array[index])
end
# now that we have the gradient we can update parameters W
W = W + eta*grad;
# report progress, monitor optimisation
@printf("Iter %d neg_loglikel=%.4f\n",iter, negative_loglikelihood(Y,X,W))
end
希望显而易见的是,我在这里尝试以最简单的方式并行计算梯度。我的策略是将梯度的计算分成尽可能多的部分,每个工作线程都需要仅处理矩阵X的一部分,该部分由first_index和last_index指定。因此,每个工作线程应使用
X[first_index:last_index,:]
进行操作。例如,对于4个工作线程和N = 10000,应按以下方式划分工作:
- 工作线程1 => first_index = 1, last_index = 2500
- 工作线程2 => first_index = 2501, last_index = 5000
- 工作线程3 => first_index = 5001, last_index = 7500
- 工作线程4 => first_index = 7501, last_index = 10000
addprocs()
添加更多工作线程,则代码的运行速度会变慢。例如,可以通过使用N=20000来创建更多数据项,从而加剧此问题。对于更多数据项,退化现象更加明显。在我的计算环境中,当N=20000并且使用一个核心时,代码运行需要约9秒。当N=20000并且使用4个核心时,需要约18秒!我尝试了许多不同的方法,受到本论坛中的问题和答案的启发,但不幸的是没有任何效果。我意识到并行化方法很幼稚,数据传输可能是问题所在,但我不知道如何正确地解决它。似乎文档在这个问题上也有些匮乏(就像Ivo Balbaert的好书一样)。
我希望您能提供帮助,因为我已经被卡住了很长时间,并且真的需要它来完成我的工作。对于任何想要运行代码的人,为了避免复制粘贴的麻烦,您可以在此处获取代码。
感谢您花费时间阅读这个非常冗长的问题!请帮我将其转化为模型答案,以便任何Julia新手都可以查阅!