バイアスと戯れる

Rと言語処理と(Rによる言語処理100本ノック終了)

Rによる言語処理100本ノック後半まとめと全体での総括

はじめに

 Rによる言語処理100本ノック(2015版)を最後まで終えることができたので、後半のまとめ記事と全体総括を書きました。Rの実行結果は下記のRPubsにアップロード済みですので、ご確認ください。


 RmdファイルはGitHubにあります(READMEも修正)。
github.com

「言語処理100本ノック」とは?

 『言語処理100本ノックは,実践的な課題に取り組みながら,プログラミング,データ分析,研究のスキルを楽しく習得することを目指した問題集です』
 (下記の公式サイトより) www.cl.ecei.tohoku.ac.jp


 プログラミング言語と言語処理の勉強に非常に有用でかつ実践的な内容ですので、皆さんもチャレンジしてみてください。

「Rによる言語処理100本ノック」後半振り返り

 前半のまとめ同様、以下では各章をRで解いた際(特にパイプ処理を活用した方法)の振り返りをそれぞれ書き、その後に後半の所感をまとめます。そして今回は全体での総括も合わせて書いています。

 (前半のまとめ同様、このまとめ記事内では基本的にコードは書きません。適宜、RPubsの記事をご参照ください)


第6章:英語テキストの処理

 Stanford CoreNLPを用いて言語処理の基本技術全体を触れる章で、英文におけるテキスト処理の流れ(ステミング・トークン化・POSタグ付けから始まり、係り受け解析や固有表現抽出から共参照解析までも)を体験できます。課題未使用の機能として感情表現分析があるので、そちらに興味がある方も試しに使ってみるといいかもしません。
 課題としては、「57. 係り受け解析」(係り受け解析木の可視化を5章から)や「59. S式の解析」(括弧の対応部分の抽出を3章から)では過去課題の処理を流用できるので、{rJava}の設定に苦しんだり解析時に落ちたりしなければ、比較的スムーズにこなせると思います(前者はRPubsに「事前準備」がもしかしたら参考になるかも)。

 Stanford CoreNLPをRで呼び出すパッケージはいくつかあり、今回はdevtools::install_github("statsmaths/coreNLP")でインストールできるものを使用しました。ただ、公式サイトでは下記が紹介されているので、こちらを使う方がいいかもしません(今回使用したパッケージでは、「56. 共参照解析」の課題をこなすには関数が足りなかったり挙動が怪しかったりして、不十分だった気がします。もしかしたら、使い方がおかしいだけかもしませんが)。

 CRAN - Package coreNLP

 また、言語処理の「全部入りパッケージ」*1には他にもApache OpenNLPがあり、{NLP}や{openNLP}でRから使用できそうです。これら以外にもCRAN Task VIEW: Natural Language Processingのフレームワーク項目にある{tm}や{RWeka}など、今後検証できればと思っている次第でございます。

第7章:データベース

 Key-Valueストアとドキュメント志向DBを使ってデータ処理する内容で、もう少しRを活用してこなしたいなと思った章です。対象とするKVSとドキュメント志向DBにはRedisとMongoを選び、{rredis}と{mongolite}を用いてデータベース処理しています。
 インメモリのRとこれらのDBは大規模データ処理において相性が良さそうで、特に文字列のような可変長データを扱う場合はドキュメント指向型データベースと組み合わせるのが適しているのではないかと(Rは可変長データの処理が不向きだと思います)。

 この章では「いかに効率よくDBへデータを挿入できるか」に、とても苦労しました(構築したDBにクエリを投げる課題は比較的簡単かと)。 Redisでは{pforeach} + {iterators}で並列化していますが、それでも一レコードずつ処理しているので時間がかかりました。対してMongoでは、jsonlite::stream_in()によるストリーミング処理で複数レコードをとても高速に書き込めました。jsonlite::stream_in()JSONを大量にパースして処理する際には活用できるので、今後も広く利用していきたいと思います。このあたりの大規模テキストデータ処理をRでうまくこなす方法は習得していく予定です。

 また、この章で一番Rを使う課題は「69. Webアプリケーションの作成」で、{shiny}でWebアプリ化しています。RPubsでは動かないのでコメントアウトしていますが、shinyapps.ioとRPubsが連携しやすいと嬉しいですね。

第8章:機械学習

 特徴設計を工夫し、ロジスティック回帰によるネガポジの感情分析(2値分類)を行う章です。タスクデータが用意され(正例と負例が同数)、分類モデルの評価結果を算出するので、データコンペのようにデータ分析テクニックを試せて力試しには良いかもしません。

 ステミングやストップワード除去といった素性エンジニアリングに注力せず、{FeatureHashing}で次元を落とした特徴ベクトルに対して、GLMによるロジスティック回帰モデル(「FH_GLM」と記述)をベースラインに、{xgboost}を用いたGradient Boostingによるロジスティック回帰モデル(「FH_BT」と記述)を試してみました(Feature Hashingによる効果を検証するには、Feature Hashingしない特徴ベクトルで構築した両モデルとも比較する必要がありますが、今回は対象外に)。
 前述のふたつのモデルで「78. 5分割交差検定」を解いた結果を書き起こしたものが下記の表です。「train」が学習データでの評価(内挿)で、「test」がテストデータで評価(外挿)を意味しています。

method type accuracy precision recall f_measure
FH_GLM train 0.6523166 0.6553770 0.6424686 0.6488586
FH_BT train 0.7976927 0.7960821 0.8004127 0.7982415
FH_GLM test 0.5040330 0.5041133 0.4942787 0.4991476
FH_BT test 0.6282123 0.6288893 0.6255862 0.6272334

 どの評価値でもGLMよりもGradient Boostingの方が良い結果でした。しかしながら、どちらもtrain-test間の差が大きく、予測に不要な変数の重みが大きくなりすぎているかもしません(過学習気味のような。{caret}をせっかく使っているのに、パラメータチューニングをあまりしていないのも要因でしょうか)。特徴設計や正則化項を考慮してモデル化({glmnet}を使用したり)など、工夫するとテスト時の評価値が上がるかもしません。

 なお、2値分類で評価が出るとROC曲線が欲しくなるので、課題にはありませんがやってみています({plotROC}を利用。インタラクティブなグラフが作成できるらしいですが、うまくできませんでした)。

 また、「71. ストップワード」ではテストを記述する課題があり、testthat::test_that()で書いていますが、testthat::describe()を使うとRSpec風に書けるそうです(要確認)。今後はパッケージ作成に力を入れていきたいので、積極的にテストを書いていきたいと思います。

第9章:ベクトル空間法 (I)

 テキストデータを整形してから単語文脈行列を作り、それを次元圧縮したベクトルから類似度を計算したり、アナロジーに用いたりする内容です。課題をこなすよりも、非常に大きな次元数を持つ行列の扱い方(ただし、0が多いスカスカな行列。疎行列と呼ばれる)と処理時間に悩まされた章とも言えます。ただ、とても苦労はしましたが、行動・購買ログや広告などビジネスで触る機会が多いと思われるデータは割とスパースになりがちで、これらの扱い方が業務に活かせる良い章だと思います。

 とはいえ、手を焼く課題はベクトルを次元圧縮するまでで、それ以降は条件を満たしたベクトルを取り出して類似度を計算するという内容で、「必要な部分だけに限定して算出」すれば割とスムーズにこなせました。ここで限定せずに全単語間で類似度を一度に求めてしまうと、単語数×単語数の行列を作り出し、メモリを使い切ってしまう危険性があるので注意です。

 また、主成分分析に関する知識が説明なしに求められるので、課題を解く前にある程度は得ておいた方がいいです。特に本課題では大規模な行列に主成分分析を適用するケースで、応じた手法や関数、パッケージを知っておかないと簡単にこなせないです(RPubsに上げた記事中の「所感」にいくつか記述)。疎行列には疎行列に対応した関数を適用させないと通常の行列に変換してメモリ使い潰す場合もあり、今回の次元圧縮以外にも機械学習系パッケージでも同様です(以前に書いた{ranger}の記事が該当しそう。{glmnet}や{xgboost}, {e1071}などは疎行列に対応した関数が定義されています)。疎行列についてはまた後日調べます。

 なお、この章で作成したコーパス(「81. 複合語からなる国名への対処」の結果)と意味ベクトル(「85. 主成分分析による次元圧縮」の単語文脈行列を次元圧縮したベクトル)、ならびに86から89までのプログラムは10章でも必要とするので、くれぐれも保存することをお忘れなく。

第10章:ベクトル空間法 (Ⅱ)

 word2vecによる単語の分散表現を使ってアナロジータスク(+クラスタリングと可視化)をこなすという章です。word2vecによる実行結果がどうしても得られず、あらかじめコマンドを実行してモデルを構築したというとても悔しい思いをしました。word2vecのC実装をラップしたRパッケージに{tmcn.word2vec}がありますが、各種パラメータの指定ができなかった点と、繰り返して類似度計算するとフリーズする症状に見舞われて使用していません。Pythonを使わないというこだわりがなければ、{PythonInR}でgensimのword2vec関数だけ呼び出してしまうというのも手としてはあります(方法は過去記事を参考)。

 課題については、word2vecによる単語の分散表現が取得さえできれば特につまずかなさそうです。9章の意味ベクトルとword2vecによる分散表現による結果をアナロジータスクで評価していますが(「93. アナロジータスクの正解率の計算」と「95. WordSimilarity-353での評価」)、両方ともword2vecの方が良い結果でした。word2vecよりも性能が良いというGloVeでも比較したいところです(下記のglove-pythonを{PythonInR}で呼んで試すことはできています)。

 maciejkula/glove-python · GitHub

 なお、本筋とは関係ないですが、RPubsに上げた記事では国名に関するベクトルをクラスタリングした結果を{leaflet}で地図にプロットしています。kmeansとhclustの結果で割り振られるクラスタ番号を指定できず配色が変わってしまい(クラスタ結果がふたつの方法で同じでも、振られる番号が違うと異なる色になってしまう)、とても悩みましたが諦めました。異なるデータ間の紐付け、悩ましい問題ですね。

総括

 後半は全体的に規模が大きいデータを扱う課題が多く、前半まとめで想定していた通り、Rで困難な場面が何度もありましたが(夜に実行したまま寝て、起きて頭を抱えるということが)、やり方や工夫で解き進めていくこと自体はできたかと思います。
 とはいえ、もう少し言語処理に適したデータ構造や入出力処理、関数群があった方が扱いやすそうです。このあたりをこなせるパッケージを作成したり、場合によっては他言語のパッケージのラッパーを作ったりと考えていますが、そのためにはもっと学習と調査が必要ですね。がんばります。




Rによる言語処理100本ノック全体での総括

 約4ヶ月をかけて、言語処理100本ノック(2015)の全てを主にパイプ処理を使ったAdvencedなRで解答しました。ただ解くのではなく、「汎用性が高くなるように」と「可読性が高くなるように」という2点を全課題で気に留めてこなしたため、想定していたよりも時間がかかってしまいましたが、非常に勉強になりました。確かにPythonを前提とした一部の課題をRでこなすのは難しかったですが、プログラミングと言語処理を合わせて実践的に学ぶにはとても良い教材だと思います。皆さんも是非挑戦してみましょう。

 ただし、解答に必要な言語処理・機械学習などの知識は課題中で触れられていないので、別途学んでおく必要があります。こちらは自分の復習を兼ねて資料をまとめておこうかと思います。
 また、すでに上げてあるRコードも終わってから見直すと修正できそうな箇所があり、こちらも合わせて直しを進めていきます。それが終わったら、言語処理100本ノックのR言語解答例用にパッケージングしておきたいですね。

 最後にこのような素晴らしい課題を作り公開してくださった岡崎先生に感謝し、より多くの人々が少しでも言語処理とそしてR言語に興味を持っていただければ幸いです。繰り返しになりますが、言語処理100本ノックはプログラミングと言語処理を実践的に学ぶのに適した教材です。皆さんも是非挑戦してみましょう。

参考

*1:「全部入り」とは生のテキストから始めて、構文解析や意味的構造を出力するもののこと。(自然言語処理における「全部入り」パッケージ - Yoh Okunoの日記