【发布时间】:2015-10-17 20:39:44
【问题描述】:
前段时间我被说服放弃了我舒适的 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 和 Xw /strong>:
####################################
## 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,工作应该被划分如下:
- worker 1 => first_index = 1, last_index = 2500
- worker 2 => first_index = 2501, last_index = 5000
- worker 3 => first_index = 5001, last_index = 7500
- worker 4 => first_index = 7501, last_index = 10000
不幸的是,如果我只有一个工人,整个代码的运行速度会更快。如果通过addprocs() 添加更多worker,代码运行速度会变慢。可以通过创建更多数据项来加剧这个问题,例如使用 N=20000。
数据项越多,降级越明显。
在我的具有 N=20000 和一个核心的特定计算环境中,代码运行时间约为 9 秒。 N=20000 和 4 个核心大约需要 18 秒!
我尝试了许多不同的方法,灵感来自这个论坛中的问题和答案,但不幸的是无济于事。我意识到并行化是幼稚的,数据移动一定是问题所在,但我不知道如何正确地做到这一点。似乎关于这个问题的文档也有点稀缺(就像 Ivo Balbaert 的好书一样)。
感谢您的帮助,因为我已经为此困扰了很长一段时间,我的工作确实需要它。任何想运行代码的人,为了省去复制粘贴的麻烦,您可以获取代码here。
感谢您抽出宝贵时间阅读这个冗长的问题!帮我把它变成一个模范答案,任何 Julia 的新手都可以参考!
【问题讨论】:
标签: parallel-processing gradient julia linear-regression