バイアスと戯れる

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

Rによる言語処理100本ノック前半まとめ

はじめに

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


 RmdファイルはGitHubにあります。
github.com


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

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


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

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

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

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


第1章:準備運動

 文字列操作の基本と言語処理タスクを交えた内容になっており、パズルを解いていく感じで個人的にはとても面白かったです。

 準備運動なのでさほど難しい問題はありませんが、つまづきそうな課題は「08.暗号文」ではないでしょうか。文字コードの扱いに慣れていないと、charToRawを使うということに気づけず、どうやればいいかわからずに挫折してしまいそうです。

 あと「04.元素記号」はマグネシムは「Mg」と思うのですが、課題通りに解くと「Mi」が出力されてしまい、悩んでしまったのは私だけでしょうか(調べても「Mi」で良いみたいでした)。

 一番最初の課題でしたので、振り返るともっといい書き方がある箇所が見受けられます。他の課題を解く間で得た知見を活用すれば、もう少しこなれた書き方にできそうなので(今なら添字操作ではなく、dplyr::sliceを使って書きます)、改めて挑戦します。

第2章:UNIXコマンドの基礎

 RよりもUNIXコマンドの書き方に悩んだ章です(UNIXコマンドをエスケープするのが地味に面倒でしたし、UNIXコマンドを呼ぶのはオススメしません)。

 ファイル操作が絡む課題が多くRだと扱いづらいかと思いましたが、{readr}を活用すれば割と手軽に進められました。神が作りたもうたパッケージは偉大です。
 ただ、「15. 末尾のN行を出力」の課題で、一度ファイルを読んでからtailで末尾N行を出すという方法しか思いつかなく、逆順で読み込むオプションがあれば便利なのかなと思いました(使い所が少ないかもしませんが)。

 RPubsの記事にも書いていますが、読み込むファイルサイズが大規模になってきたり、対象ファイルが複数になってきたら、{foreach}や{pforeach}を{iterators}を組み合わせて使うとよいかもしません。

 第1章同様修正したい箇所がいくつかあるので、時間を見つけて直していきます。

第3章:正規表現

 Wikipedia記事を題材に正規表現を学ぶ章で、使用するプログラミング言語への依存度は割と低く、幅広く使えそうな内容です(ただし、正規表現でマッチさせた文字列の後処理は各プログラミング言語に依存します)。
 定数を変えれば他の国名にも対応しているかと思いますので、興味がある方はお試しください。

 正規表現が書ければ比較的スムーズに進みますが、「25. テンプレートの抽出」で入れ子構造のある{ }といくつかの種類のある<ref>タグの扱いに悩むかもしません(どこまでが値か限定しづらかった。特に<ref>タグ間で改行を含んでいて、単純に改行で区切れなかった)。

 また、「29. 国旗画像のURLを取得する」の課題では、せっかくなので取得したURLのsvgファイルをプロットしています(URL取得時のJSON処理は{jsonlite}を使用)。
 今回は{rvest}でスクレイピング処理せず、libxml2ベースの{xml2}のread_htmlを使っています({rvest}のhtmlは0.4で廃止予定で現状は{xml2}のread_htmlを呼んでいるようでしたので、{xml2}だけで充分できるのではないかとやってみました)。
 このとき、svgファイルをRで読み込んで表示させるためのパッケージがよくわからなかったので、ファイル名を固定してマークダウン上で指定して表示させました。うまくやる方法をもう少し調べて修正したいと思います。

第4章:形態素解析

 形態素解析の結果を用いて品詞毎に処理・分析する章で、{Rcpp}を用いてMeCabによる形態素解析結果を取得しています。手軽にやるのであれば、石田先生の{RMeCab}を活用した方が断然いいです。

 課題全般で引数を{lazyeval}で定義してNSEで渡して処理するコードを多用しており、非常に読みにくくなっていますのでご注意ください(dplyr::mutate_の.dots引数にsetNamesを使う自分のメモ用と化している。汎用性がとても高くなるので、覚えておくと便利ではあります)。

 ある条件を満たした原形の抽出やプロット周りでつまづく課題ではないと思うので、悩むとしたら「35. 名詞の連接」かと思います(「AのB」は助詞の「の」の前後の品詞を確認すればOK)。
 私が取った方策は「AのB」を拡張するアプローチで、名詞連接しているかどうかを判定(0 or 1)して文単位で累積和を計算するという方法で解いています(Webログに応用すると、サイト訪問ユーザーのセッション区切りをSQLだけで行えます)。

 形態素解析器を実装するのは言語処理屋さんの基本という言葉をどこかで拝見したので、Rでチャレンジしてみたいものです(その前にRからMeCabやCaboCha, kuromojiを使うパッケージに致命的なバグがあったので修正します)。

第5章:構文解析

 係り受け解析の結果を用いて係り受けや格のパターン解析やパスを抽出する章で、{Rcpp}でC++11を書いてCaboChaによる係り受け解析結果を取得しています(一番苦しんだ章です)。手軽にやるのであれば、石田先生の{RCaBoCha}を活用した方が断然いいです。

 RPubsに上げた記事の「事前準備」の項目にある、係り受け解析でとても時間がかかってしまいました(コマンドライン上でファイルを渡して処理する際と比べて10倍は遅い)。自分のC++実装が悪いと思うので、{Rcpp}とC++11の書き方を調べて修正します({Rcpp}でのスマートポインタの使い方の説明がほとんどなく、とても苦しみました)。

 また、クラスを定義して活用するのが前提の課題となっており、{R6}を用いてクラス定義していますが、クラス化によるメリットを享受できていません。係り受け関係やパスの取得にて非常に有効と考えられるので、活用できるように学習していきます。

 課題に関しては基本的には4章と同じアプローチ(ある条件にマッチする文節を抽出して処理)ですが、全体的に課題文の解釈がとても難しかったです。
 特に「49. 名詞間の係り受けパスの抽出」は、問題文の理解だけでもとても時間がかかりました。解釈を書き下したメモをRPubsの記事に残しましたので、同じ悩みで行き詰まった方はご参照ください(調べてみると挑戦している他の方々も解釈に悩んでいるようです)。
 さらに解き方もアドホックなもので、他の文でも適用できる保証が全くありませんので、くれぐれもご注意ください。
 (「48. 名詞から根へのパスの抽出」にて再帰でパスを探索している箇所もメモ化や動的計画法を考慮すべきなので、要修正)

 上記の通り、自信がまるでない解答だったり、処理が遅かったりしますので、やり直す可能性が非常に高い章です。

総括

 言語処理100本ノック(2015)はPythonを前提とした課題があり、Rと合わない箇所もありますが、プログラミングと言語処理を合わせて学ぶにはとても良い教材だと思います(これに触発され、「R言語100本ノック」を作ろうかとも思案中)。

 問題を作られた岡崎先生のコーディングに関する考え方も非常に参考になるので、下記の資料は読んでおくといいかもしません(NLP2013のチュートリアル発表なので、途中出てくる問題は2015年版ではありません)。
  研究者流 コーディングの極意


 R言語では難しいかと最初は思いましたが、{stringr}やパイプ処理の組み合わせで充分にこなせなくはないという印象です(あとは大規模に処理できれば)。
 厄介だったのは、「単語からなる文字列ベクトル(c("我輩", "は", "猫", "で", "ある") )」と「長さが1で空白で区切られた文字列("我輩 は 猫 で ある")」によって、扱い方を変えないといけない点でしょうか。
 他にも{stringr}の関数の戻り値が行列でそのままパイプ処理を繋げられないなど、型やクラスに苦しめられる箇所もありました。{stringr}のアップデートで解決できると嬉しいところです。

 現在は6章まで終了していますので、引き続き進めていきます。