R 3.4.0 になり, for文がデフォルトでコンパイルされることで

以前に比べて R の計算速度は早くなりました。

 

何よりも R の計算のボトルネックはとにかく for文でした。

(いかに for文を使わずにプログラミングできるかが

結構重要なスキルだった時代も、、、・)

 

そんな高速化している R ですが、

iteration × Cross validation が当たり前になるなど

それ以上の速度で我々が扱う計算は

タスクが大きくなってます。

 

ゆえに相変わらず重要なのが

「並列化」。

 

特に、最近のPCでは10個近い

並列計算が簡単になってきていて

これを使わない手はありません。

 

というわけで

以前の個人HPでも紹介していた

Rで “簡単に” 並列計算をする方法を

改めて紹介します。

 

簡単に内容をまとめると、、、

・foreach と doParallel というパッケージを使うと

並列計算が超簡単

・何が簡単かというと、

「各パターンごとに計算」というのが

シンプルに実行できる

・i.e. Monte Carlo シミュレーションには最適

という感じ。

 

というわけで実装してみました。

プログラム内容は

・coordinate descent algorithm (glmnetパッケージ)

・elastic net

・10-fold cross-validation (alpha と lambda の grid 検索)

です。

 

ちなみに glmnet パッケージも進化しており、

最近ではデフォルトで iterators パッケージと

foreach パッケージを読み込むようになりました。

 

 

【準備:CRAN から package をゲット】

1. glmnet:https://cran.r-project.org/web/packages/glmnet/index.html

(elastic net の計算で使用. 並列計算とはあまり関係なし)

2. iterators:http://cran.r-project.org/web/packages/iterators/index.html

(下の foreach のために必要らしい. glmnet を使うなら不要)

3. foreach:http://cran.r-project.org/web/packages/foreach/index.html

(glmnet を使うなら不要)

4. doParallel:http://cran.r-project.org/web/packages/doParallel/index.html

 

 

【プログラム】

library(glmnet)
library(MASS)
library(iterators)
library(foreach)
library(doParallel)
registerDoParallel(detectCores())
 
### 人工データ作成
n		<- 50
p		<- 100
sigma	<- 5
sigma2	<- sigma**2
rho		<- 0.5
t_beta	<- c(rep(3, ceiling(p/2)), numeric(p-ceiling(p/2)))
Sigma	<- diag(p)
for(i in 1:p){
	for(j in 1:p){
		Sigma[i,j]		<- rho**abs(i-j)
	}
}
x		<- mvrnorm(n, numeric(p), Sigma)
H		<- diag(n) - matrix(rep(1/n, n**2), nrow=n)
x		<- H %*% x
x		<- x %*% diag(sqrt(n)/sqrt(colSums(x**2)))
 
y		<- x %*% t_beta + rnorm(n, 0, sigma)
y		<- y-mean(y)

### 候補の lambda と alpha の数の決定(今回は 100×100 の grid search)
lg.lam	<- 100
lg.al	<- 100
 
### k-fold cross-validation の k
K		<- 10
 
### alpha fix での k-fold cross-validation
kcv.en	<- function(x, y, alpha, lg.lam, K){
	n			<- nrow(x)
	p			<- ncol(x)
	y			<- drop(y)
 
	lambda.max	<- max(abs(t(x) %*% y))/(n*alpha)
	lambda.min	<- 0.01
	lambdas		<- (exp(((log(lambda.max)-log(lambda.min))
						* ((lg.lam-1):0)/(lg.lam-1)) + log(lambda.min)))

	PSE			<- matrix(numeric(lg.lam*K), nrow=K)

	n1			<- n
	N			<- matrix(numeric(2*n), nrow=n)
	Ind			<- sample(1:n, n, rep=FALSE)
	N[,1]		<- Ind
	for(k in 1:K){
		n2					<- ceiling(n1/(K-k+1))
		N[(1:n2)+(n-n1),2]	<- k
		n1					<- n1 - n2
	}
	for(k in 1:K){
		X.est				<- x[N[,2] != k, ]
		y.est				<- y[N[,2] != k]
		X.evl				<- x[N[,2] == k, ]
		y.evl				<- y[N[,2] == k]
		GLMNET				<- glmnet(X.est, y.est, family=\"gaussian\", alpha=alpha, lambda= lambdas)
		Beta				<- t(matrix(as.numeric(GLMNET$beta), nrow=p))
		y.prd				<- X.evl %*% t(Beta)
		PSE[k,]				<- colSums((y.prd - y.evl)**2)
	}
	CV				<- colSums(PSE)/K
 
	### CV の値と lambda の値を返す関数
	return(list(cv=CV, lambda=lambdas))
}
 
alphas	<- (1:lg.al)/lg.al
CV		<- matrix(numeric(lg.lam*lg.al), ncol=lg.lam)
LAMBDA	<- matrix(numeric(lg.lam*lg.al), ncol=lg.lam)
 
### 並列計算なし
TIME1	<- system.time(for(i in 1:lg.al){
	alpha			<- alphas[i]
	res				<- kcv.en(x, y, alpha, lg.lam, K)
	CV[i,]			<- res$cv
	LAMBDA[i,]		<- res$lambda
})
image(CV)
 
### 並列計算あり
f.kcv	<- function()
{
	foreach(i=1:lg.al, .export=ls(envir=parent.frame())) %dopar% {
		alpha				<- alphas[i]
        kcv.en(x, y, alpha, lg.lam, K)
	}
}
 
f.para	<- function(lg.lam, lg.al){
	res				<- f.kcv()
	CV.p				<- matrix(numeric(lg.lam*lg.al), ncol=lg.lam)
	LAMBDA.p		<- matrix(numeric(lg.lam*lg.al), ncol=lg.lam)
	for(i in 1:lg.al){
		CV.p[i,]				<- res[[i]]$cv
		LAMBDA.p[i,]			<- res[[i]]$lambda
	}
	return(list(CV=CV.p, LAMBDA=LAMBDA.p))
}
TIME2	<- system.time(RES <- f.para(lg.lam, lg.al))

 

以上のプログラムで

並列計算ありとなしで時間を計測したところ、

・並列なし:4.306

・並列あり:1.234

とだいたい3〜4倍速いという結果に!!!

(ちなみにPC環境はコア数8)

 

が、いくつかの場面でこの方法を使用したところ

以下の注意点に気づきました

 

1. 必ずしも並列計算の方が速いわけではない

:ジョブを分割するためか、

実際の計算以外の時間もかかるらしく、

簡単なプログラムでは並列なしの方が速いことも。

 

2. メモリオーバーに注意!

:大きなデータを扱う場合、

ジョブを分割した分だけメモリが必要に。

その結果、メモリオーバーを起こしたのか

エラーが返ってくることが何度かありました。

 

 

というわけで今回は私でもできる

Rでの簡単並列計算の方法を紹介致しました。

 

また、並列計算と同じく

「複数マシン」を使うものとして

分散処理がありますが

最近では Hadoop以外にも選択肢があるようで、

 

Rとの連携という意味では Spark が

中々便利そうです。

(Spark についてもは今後勉強していくので

また機会があれば共有します。)

 

 

みなさんもRで並列計算をガンガン使ってみてくださいな。

 

参考文献:

http://d.hatena.ne.jp/teramonagi/20140920/1411195147