第51回R勉強会@東京(TokyoR)にてLT発表しました
概要
10月10日に開催されたTokyoRのLTセッションにて、『Rでいろんな言語』というタイトルでRからPythonを呼ぶパッケージ{PythonInR}を紹介しました。発表の途中でスクリーンが砂嵐になるというトラブルに見舞われましたが、なんとか無事に時間内に収めることができました。
また、公開したスライドに未掲載だった{PythonInR}を使ってChainerを呼び出したコードを手直ししたので、メモ書きとして残しておきます。
発表について
LT発表スライドの概要は次の通りです。
- 2015年10月10日はTokyoRとPyConJPが同日に開催されました
- とても素晴らしいPythonを脆弱なRから活用できたらRユーザーは幸せだけど、できないのだろうか?
- {PythonInR}というパッケージがあり、関数や環境が充実しているようで、試してみたら呼べてしまいました
以上のような、開催日にちなんだ発表をさせていただきました。
{PythonInR}はドキュメントが充実しており、そちらをまとめる&実行してみた内容がメインになっております。
PythonInR Reference — PythonInR 1.0.0 documentation
そして、PythonパッケージをRで使用する例として、スライドではgensimのword2vec()で単語の分散表現を学習していますが、paragraph2vecもgensimから呼び出せました(発表スライド中のコマンドとともにRPubsへ後日公開予定)。
また、Chainerによるword2vecは自前のMac PCでも動作しましたが(複数インストールされているPythonで苦しみましたが)、GloVeのPython実装はLinux上でのみ確認しております(以前にCaffeのインストールで、gccをいろいろやったせいかも。こちらのスクリプトも調査して後日公開予定)。
maciejkula/glove-python · GitHub
{PythonInR}を使ってChainerを動かす
以下はLT発表後に手直ししたChainerを用いたword2vecを行うRコード(ほぼPython)です。
各種PythonコードはChainerのexamples/word2vec/train_word2vec.pyを参考にさせていただいております。
chainer/train_word2vec.py at master · pfnet/chainer · GitHub
Pythonコード(関数定義)
# def-chainer-word2vec-fun.py def continuous_bow(dataset, position, window): h = None w = np.random.randint(window - 1) + 1 for offset in range(-w, w + 1): if offset == 0: continue d = xp.asarray(dataset[position + offset]) x = chainer.Variable(d) e = model.embed(x) h = h + e if h is not None else e d = xp.asarray(dataset[position]) t = chainer.Variable(d) return loss_func(h, t) def skip_gram(dataset, position, window): d = xp.asarray(dataset[position]) t = chainer.Variable(d) w = np.random.randint(window - 1) + 1 loss = None for offset in range(-w, w + 1): if offset == 0: continue d = xp.asarray(dataset[position + offset]) x = chainer.Variable(d) e = model.embed(x) loss_i = loss_func(e, t) loss = loss_i if loss is None else loss + loss_i return loss
Rコード(準備)
# コサイン類似度 filterCosineSim <- function ( seed_word_vector, target_word_vectors, extract_rownames = NULL ) { word_vectors <- rbind(seed_word_vector, target_word_vectors) numerator <- crossprod(x = t(x = word_vectors)) denominator <- diag(numerator) return((numerator / sqrt(outer(denominator, denominator)))[extract_rownames, ]) } # Rパッケージ読み込み library(PythonInR) # Python側で使うライブラリ群の読み込み PythonInR::pyImport(import = "time") PythonInR::pyImport(import = "collections") PythonInR::pyImport(import = "numpy", as = "np") PythonInR::pyImport(import = "chainer") PythonInR::pyImport(from = "chainer", import = "cuda") PythonInR::pyImport(import = "chainer.functions", as = "F") PythonInR::pyImport(import = "chainer.optimizers", as = "O") PythonInR::pyExecp(code = 'xp = np') PythonInR::pyExecfile(filename = "def-chainer-word2vec-fun.py") # パラメータや引数の設定 ## 明示的にas.integer()で整数にしないとfloatになる PythonInR::pySet(key = "window", value = as.integer(5)) PythonInR::pySet(key = "unit", value = as.integer(200)) PythonInR::pySet(key = "batchsize", value = as.integer(100)) PythonInR::pySet(key = "set_epoch", value = as.integer(10)) # 入力ファイルもexamplesと同じものを使う ## https://raw.githubusercontent.com/tomsercu/lstm/master/data/ptb.train.txt PythonInR::pySet(key = "train_file_name", value = "ptb.train.txt")
# データ準備(Rで書いてもよさそう) read_file <- ' word2index = {} index2word = {} dataset = [] counts = collections.Counter() with open(train_file_name) as f: for line in f: for word in line.split(): if word not in word2index: ind = len(word2index) word2index[word] = ind index2word[ind] = word counts[word2index[word]] += 1 dataset.append(word2index[word]) n_vocab = len(word2index) ' PythonInR::pyExec(code = read_file) # R上でデータを確認(型キャストされる) ## http://pythoninr.bitbucket.org/TypeCasting.html#r-to-python-pyget > PythonInR::pyGet(key = "index2word")[1:20] 0 1 2 3 4 5 "aer" "banknote" "berlitz" "calloway" "centrust" "cluett" 6 7 8 9 10 11 "fromstein" "gitano" "guterman" "hydro-quebec" "ipo" "kia" 12 13 14 15 16 17 "memotec" "mlx" "nahb" "punts" "rake" "regatta" 18 19 "rubens" "sim" # データ数 > PythonInR::pyExec(code = 'print(len(dataset))') 9999
Rコード(学習部)
# モデルと損失関数の定義 PythonInR::pyExecp(code = 'model = chainer.FunctionSet(embed = F.EmbedID(n_vocab, unit),)') # (「Skip-gramモデルとSoftmax+CE」の組み合わせ。examplesのプログラムには他の計算方法もある) PythonInR::pyExecp(code = 'train_model = skip_gram') # PythonInR::pyExecp(code = 'train_model = continuous_bow') def_loss_func <- ' model.l = F.Linear(unit, n_vocab) loss_func = lambda h, t: F.softmax_cross_entropy(model.l(h), t) ' PythonInR::pyExec(code = def_loss_func)
# 学習の実行 run_optimizer <- ' dataset = np.array(dataset, dtype = np.int32) # 最適化手法にはAdamを使う optimizer = O.Adam() optimizer.setup(model) word_count = 0 skip = (len(dataset) - window * 2) // batchsize # 経過表示用 begin_time = time.time() cur_at = begin_time next_count = 100000 for epoch in range(set_epoch): accum_loss = 0 indexes = np.random.permutation(skip) print("epoch: {0}".format(epoch)) for i in indexes: # 経過表示 if word_count >= next_count: now = time.time() duration = now - cur_at throuput = 100000. / (now - cur_at) print("{} words, {:.2f} sec, {:.2f} words/sec".format(word_count, duration, throuput)) next_count += 100000 cur_at = now position = np.array(range(0, batchsize)) * skip + (window + i) loss = train_model(dataset, position, window) accum_loss += loss.data word_count += batchsize optimizer.zero_grads() loss.backward() optimizer.update() print(accum_loss) ' # 学習実行 > PythonInR::pyExec(code = run_optimizer) epoch: 0 5075.76473427 epoch: 1 4424.79116631 epoch: 2 4015.50499153 epoch: 3 4136.20912743 epoch: 4 3990.47239399 epoch: 5 3487.66871929 epoch: 6 3219.87060261 epoch: 7 3205.53463268 epoch: 8 2983.06294155 epoch: 9 2466.28941822
Rコード(結果の取得)
PythonInR::pyExec(code = 'model.to_cpu()') embed_word_res <- PythonInR::pyGet(key = 'model.embed.W') rownames(embed_word_res) <- names(sort(PythonInR::pyGet(key = 'word2index'))) > t(embed_word_res[1:5, 20]) aer banknote berlitz calloway centrust [1,] -1.9210659266 2.66550684 0.319574237 0.381956309 0.577695131 [2,] -1.3218621016 -2.49230599 -0.679477215 0.139727041 0.136762887 [3,] 0.3553474247 -1.27228832 -0.015422379 0.154143527 0.941983998 [4,] 0.5452865958 -0.57457387 -1.342149138 1.199438214 -0.491248012 [5,] -1.4254230261 -0.46078750 0.217225134 0.746220648 1.651308060 [6,] -0.2459839880 -1.02575839 0.861322761 1.275416017 0.504857481 [7,] 0.6889060736 0.62762350 1.335024953 -1.237316370 0.422370821 [8,] 0.4057052135 0.89054787 -2.274557590 1.760630727 1.280030966 [9,] -0.5444009304 0.31793687 1.741585135 0.085238419 -1.072679877 [10,] 0.0153995762 -1.40665817 -1.653285742 -0.471961915 -0.945782900 [11,] 0.2586796284 -2.63728619 0.974781752 -1.091140628 0.711577356 [12,] 1.2850890160 0.92642528 0.059416633 1.979779959 1.517618895 [13,] -1.0132664442 -1.57576632 -0.084011830 1.128095150 1.733328104 [14,] 0.6541242003 -0.28958291 -2.761154890 0.630589366 -0.688703120 [15,] -0.4360258877 -1.11609161 0.149192870 0.148330510 -0.382760823 [16,] 0.4184043705 -0.96482414 -0.007774435 -0.421285033 -0.534063876 [17,] -0.9240003228 1.18594503 0.470627725 0.278243899 0.336607963 [18,] -1.0976681709 0.59142280 -0.482638657 -1.455917716 0.520051181 [19,] 1.2184268236 -0.32253051 1.953751206 -1.087785363 -0.101868734 [20,] 0.6005632281 0.90789568 1.657281041 1.603208303 -0.275181383
Rコード(アナロジー)
# "sister" + "husband" - "brother" seed_word_vector <- matrix( data = -embed_word_res["brother", ] + embed_word_res["sister", ] + embed_word_res["husband", ], nrow = 1, dimnames = list("-brother+sister+husband", NULL) ) seed_word_cs <- filterCosineSim( seed_word_vector = seed_word_vector, target_word_vectors = embed_word_res, extract_rownames = rownames(seed_word_vector) ) # "wife"が出てきて欲しいが(TOP3は演算に使用しているので除外) sort( x = seed_word_cs[setdiff(x = names(seed_word_cs), names(seed_word_vector))], decreasing = TRUE )[1:5] -brother+sister+husband husband sister unveil 1.0000000 0.5998873 0.4992677 0.2933514 feb. 0.2673408
まとめ
{PythonInR}というRからPythonを呼ぶパッケージを使い、Chainerのword2vecのデモを動かすRコードを書きました。Chainerが動作したので、RユーザーでもDeep Learningの恩恵が享受しやすくなりました。また、Chainer以外のライブラリも挙動を確認しているので、RにはなくてPythonにあるというパッケージを試したいという方々は今まで以上に手を広げやすくなったかと思います。
とはいえ、Pythonコードを書くシーンが多いので、これを機にPythonの勉強を始めるのもいいかもしませんね。
あと、学習した分散表現を用いたアナロジーがあまりいい結果でなかったので、書き方ややり方を間違えていたかもしません。こちらに関しては、もう少し調査をしたいと思います。
参考
その他
RでPythonを呼ぶ方法として、他にも{Rcpp}を使う例もあります。興味がある方は下記のリンクをご覧ください。
Call Python from R through Rcpp
LT発表後に{PythonInR}に興味持たれた@kohske 先生が .RmdファイルでPythonチャンクを書き、それをRチャンク内で呼び出す仕組みを試みております。
RPubs - RMarkdown de PythonInR
https://gist.githubusercontent.com/kohske/f4b8a828da5da7c74717/raw/f17248be43d3e61bf8d8de7e05bc1d17d2a070cb/pynR.Rmd
そのTLを追って知った{runr}も興味深いですね。
実行環境
> devtools::session_info() Session info ----------------------------------------------------------------------------------------- setting value version R version 3.2.2 (2015-08-14) system x86_64, darwin13.4.0 ui RStudio (0.99.467) language (EN) collate ja_JP.UTF-8 tz Asia/Tokyo Packages --------------------------------------------------------------------------------------------- package * version date source curl 0.9 2015-06-19 CRAN (R 3.2.0) devtools 1.8.0 2015-05-09 CRAN (R 3.2.0) digest 0.6.8 2014-12-31 CRAN (R 3.2.0) git2r 0.10.1 2015-05-07 CRAN (R 3.2.0) memoise 0.2.1 2014-04-22 CRAN (R 3.2.0) pack 0.1-1 2015-04-21 local PythonInR * 0.1-1 2015-09-19 CRAN (R 3.2.0) R6 2.1.1 2015-08-19 CRAN (R 3.2.0) Rcpp 0.12.0 2015-07-26 Github (RcppCore/Rcpp@6ae91cc) rversions 1.0.1 2015-06-06 CRAN (R 3.2.0) xml2 0.1.1 2015-06-02 CRAN (R 3.2.0)