Japan.R 2015にてLT発表しました
概要
記事を書くのが遅くなりましたが、12月5日に開催されたJapan.RのLTセッションにて、『Rと形態素解析』というタイトルで形態素解析の説明と、Rで形態素解析ならびにわかち書きする方法を紹介しました。総ページ数80オーバーでしたが、なんとか無事に終えることができました。運営の方々、参加者の皆さん、お疲れさまでした。
今回は参照しやすいように資料内に書いたRコードを、ブログに記述しました。
発表について
LT発表スライドの概要は次の通りです。
以上のような発表をさせていただきました。
形態素解析についての説明は勉強を兼ねた資料になっており、LT時には時間の関係上、飛ばさせていただきました。
形態素解析の仕組みや辞書引きの話、コスト設定などから、点推定による形態素解析や未知語処理、教師なし形態素解析やRNN-LSTMを用いた形態素解析など最近の話題についてもまとめてみました。言語モデルとスムージング、隠れマルコフモデルとCRFについては省略してしまいましたが、ご興味のある方はお読みください。
Rコードについて
資料内で記載した、Rから形態素解析器を呼び出すコードを下記に掲載しました。試してみたいと思った方はお使いください。
RからMeCabを使う{Rcpp}編
library(Rcpp) # MeCabのC++ APIのRcppコード # MeCabタガー生成の索性レベルは2 # 表層文字列と素性のみを形態素解析結果として受け取る # http://taku910.github.io/mecab/bindings.html rcpp_src <- ' Rcpp::DataFrame executeMecab(SEXP str) { using namespace Rcpp; using namespace MeCab; std::string input = Rcpp::as<std::string>(str); std::vector<std::string> surface, feature; MeCab::Tagger *tagger = MeCab::createTagger("-l 2"); const MeCab::Node *node(tagger->parseToNode(input.c_str())); for (; node; node = node->next) { if (node->stat != MECAB_BOS_NODE) { surface.push_back(std::string(node->surface, node->length)); feature.push_back(std::string(node->feature)); } } delete tagger; return Rcpp::wrap( Rcpp::DataFrame::create( Rcpp::Named("surface") = surface, Rcpp::Named("feature") = feature ) ); } ' Sys.setenv("PKG_LIBS" = "-lmecab") executeMecab <- Rcpp::cppFunction( code = rcpp_src, includes = c("#include <mecab.h>") ) SET_INPUT_STRING <- c("この先生きのこれるか") > executeMecab(str = SET_INPUT_STRING) surface feature 1 この 連体詞,*,*,*,*,*,この,コノ,コノ 2 先 名詞,一般,*,*,*,*,先,サキ,サキ 3 生き 動詞,自立,*,*,一段,連用形,生きる,イキ,イキ 4 のこれる 動詞,自立,*,*,一段,基本形,のこれる,ノコレル,ノコレル 5 か 助詞,副助詞/並立助詞/終助詞,*,*,*,*,か,カ,カ 6 BOS/EOS,*,*,*,*,*,*,*,*
RMeCabを使う
# install.packages ("RMeCab", repos = “http://rmecab.jp/R”) library(RMeCab) > RMeCab::RMeCabC(str = SET_INPUT_STRING) %>% + unlist(use.names = TRUE) 連体詞 名詞 動詞 動詞 助詞 "この" "先" "生き" "のこれる" "か"
やはり、{RMeCab}を使いましょう。
RからKyTeaを使う{Rcpp}編
rcpp_kytea_src <- ' Rcpp::List executeKytea(SEXP str) { using namespace Rcpp; using namespace std; using namespace kytea; std::string input = Rcpp::as<std::string>(str); std::vector<std::string> surface; std::vector<std::vector<std::map<std::string, double>>> feature; kytea::Kytea kytea; KyteaConfig* config = kytea.getConfig(); kytea.readModel(config->getModelFile().c_str()); StringUtil* util = kytea.getStringUtil(); // 一文を表すオブジェクトを作る KyteaString surface_string = util->mapString(input.c_str()); KyteaSentence sentence(surface_string, util->normalize(surface_string)); kytea.calculateWS(sentence); const KyteaSentence::Words & words = sentence.words; // 各タグ種類に対してタグ付与を行う for (auto i : boost::irange(0, int(config->getNumTags()))) { kytea.calculateTags(sentence, i); } for (auto i : boost::irange(0, (int)words.size())) { surface.push_back(util->showString(words[i].surface)); // 各タグ種に対して std::vector<std::map<std::string, double>> tag_vec; for (auto j : boost::irange(0, (int)words[i].tags.size())) { std::map<std::string, double> tag_pairs; for (auto k : boost::irange(0, (int)words[i].tags[j].size())) { tag_pairs[util->showString(words[i].tags[j][k].first)] = words[i].tags[j][k].second; } tag_vec.push_back(tag_pairs); } feature.push_back(tag_vec); } return Rcpp::List::create( Rcpp::Named("surface") = surface, Rcpp::Named("feature") = feature ); } ' Sys.setenv("PKG_LIBS" = "-lkytea") executeKytea <- Rcpp::cppFunction( code = rcpp_kytea_src, includes = c( "#include <boost/range/irange.hpp>", "#include <kytea/kytea.h>", "#include <kytea/kytea-struct.h>", "#include <kytea/string-util.h>" ), plugins = "cpp11" ) SET_INPUT_STRING <- c("今日もしないとね") > kytea_result <- executeKytea(str = SET_INPUT_STRING) %>% + print $surface [1] "今日" "も" "し" "な" "い" "と" "ね" $feature $feature[[1]] $feature[[1]][[1]] ローマ字文 代名詞 名詞 0.00000000 -0.01478629 3.46305162 $feature[[1]][[2]] きょう こんにち 1.124308 0.000000 $feature[[2]] $feature[[2]][[1]] ローマ字文 助詞 言いよどみ 0.0000000 2.9044017 -0.4293123 $feature[[2]][[2]] も 100 $feature[[3]] $feature[[3]][[1]] ローマ字文 代名詞 動詞 0.0000000 -0.6963154 2.5347444 $feature[[3]][[2]] し たし 0.9999615 0.0000000 $feature[[4]] $feature[[4]][[1]] ローマ字文 助動詞 形容詞 0.000000 2.311590 -0.171657 $feature[[4]][[2]] な らな 0.9999511 0.0000000 $feature[[5]] $feature[[5]][[1]] ローマ字文 動詞 語尾 0.0000000 -0.4927065 2.8994729 $feature[[5]][[2]] い 100 $feature[[6]] $feature[[6]][[1]] ローマ字文 助詞 形状詞 0.0000000 4.0511042 -0.3718668 $feature[[6]][[2]] と 100 $feature[[7]] $feature[[7]][[1]] ローマ字文 助詞 空白 0.000000 2.512480 -0.354871 $feature[[7]][[2]] ね 100 # スコアが高いタグに限定 > dplyr::bind_cols( dplyr::data_frame(surface = kytea_result$surface), dplyr::bind_rows( lapply( X = kytea_result$feature, FUN = function (features) { return( dplyr::data_frame( pos = names(x = which.max(features[[1]])), org = names(x = which.max(features[[2]])) ) ) } ) ) ) Source: local data frame [7 x 3] surface pos org 1 今日 名詞 きょう 2 も 助詞 も 3 し 動詞 し 4 な 助動詞 な 5 い 語尾 い 6 と 助詞 と 7 ね 助詞 ね
RからKuromojiを使う
library(rJava) # jarファイルをあらかじめ用意しておく SET_KUROMOJI <- list( CLASS_JAR_PATH = stringr::str_c(getwd(), "/resource/kuromoji-0.7.7.jar") ) SET_INPUT_STRING <- c("関西国際空港から出発した") executeKuromoji <- function (str, set_mode = NULL, jar_path) { if (is.null(str) || str == "") { return (NULL) } rJava::.jinit(classpath = "", force.init = FALSE) rJava::.jaddClassPath(path = jar_path) mode <- rJava::J(class = "org.atilika.kuromoji.Tokenizer$Mode") builder <- rJava::J(class = "org.atilika.kuromoji.Tokenizer", method = "builder") if (!is.null(set_mode)) { if (set_mode == "SEARCH") { builder <- builder$mode(mode$SEARCH) } else if (set_mode == "EXTENDED") { builder <- builder$mode(mode$EXTENDED) } else { set_mode <- "NORMAL" } } else { set_mode <- "NORMAL" } tokenizer <- builder$build() tokened <- tokenizer$tokenize(str) return ( dplyr::bind_rows( lapply(X = as.list(tokened), FUN = function (token) { return( dplyr::data_frame( surface = token$getSurfaceForm(), feature = token$getAllFeatures(), is_know = token$isKnown(), is_unk = token$isUnknown(), is_user = token$isUser(), mode = set_mode ) ) }) ) ) } # モードを変えて実行する # NORMAL > executeKuromoji( + str = SET_INPUT_STRING, + set_mode = NULL, + jar_path = SET_KUROMOJI$CLASS_JAR_PATH + ) Source: local data frame [5 x 6] surface feature is_know is_unk is_user mode 1 関西国際空港 名詞,固有名詞,組織,*,*,*,関西国際空港,カンサイコクサイクウコウ,カンサイコクサイクーコー TRUE FALSE FALSE NORMAL 2 から 助詞,格助詞,一般,*,*,*,から,カラ,カラ TRUE FALSE FALSE NORMAL 3 出発 名詞,サ変接続,*,*,*,*,出発,シュッパツ,シュッパツ TRUE FALSE FALSE NORMAL 4 し 動詞,自立,*,*,サ変・スル,連用形,する,シ,シ TRUE FALSE FALSE NORMAL 5 た 助動詞,*,*,*,特殊・タ,基本形,た,タ,タ TRUE FALSE FALSE NORMAL # SEARCH > executeKuromoji( + str = SET_INPUT_STRING, + set_mode = "SEARCH", + jar_path = SET_KUROMOJI$CLASS_JAR_PATH + ) Source: local data frame [7 x 6] surface feature is_know is_unk is_user mode 1 関西 名詞,固有名詞,地域,一般,*,*,関西,カンサイ,カンサイ TRUE FALSE FALSE SEARCH 2 国際 名詞,一般,*,*,*,*,国際,コクサイ,コクサイ TRUE FALSE FALSE SEARCH 3 空港 名詞,一般,*,*,*,*,空港,クウコウ,クーコー TRUE FALSE FALSE SEARCH 4 から 助詞,格助詞,一般,*,*,*,から,カラ,カラ TRUE FALSE FALSE SEARCH 5 出発 名詞,サ変接続,*,*,*,*,出発,シュッパツ,シュッパツ TRUE FALSE FALSE SEARCH 6 し 動詞,自立,*,*,サ変・スル,連用形,する,シ,シ TRUE FALSE FALSE SEARCH 7 た 助動詞,*,*,*,特殊・タ,基本形,た,タ,タ TRUE FALSE FALSE SEARCH # EXTENDED > executeKuromoji( + str = SET_INPUT_STRING, + set_mode = "EXTENDED", + jar_path = SET_KUROMOJI$CLASS_JAR_PATH + ) Source: local data frame [7 x 6] surface feature is_know is_unk is_user mode 1 関西 名詞,固有名詞,地域,一般,*,*,関西,カンサイ,カンサイ TRUE FALSE FALSE EXTENDED 2 国際 名詞,一般,*,*,*,*,国際,コクサイ,コクサイ TRUE FALSE FALSE EXTENDED 3 空港 名詞,一般,*,*,*,*,空港,クウコウ,クーコー TRUE FALSE FALSE EXTENDED 4 から 助詞,格助詞,一般,*,*,*,から,カラ,カラ TRUE FALSE FALSE EXTENDED 5 出発 名詞,サ変接続,*,*,*,*,出発,シュッパツ,シュッパツ TRUE FALSE FALSE EXTENDED 6 し 動詞,自立,*,*,サ変・スル,連用形,する,シ,シ TRUE FALSE FALSE EXTENDED 7 た 助動詞,*,*,*,特殊・タ,基本形,た,タ,タ TRUE FALSE FALSE EXTENDED
{rTinySegmenter}の紹介
TinySegmenterはJavaScriptだけで書かれたコンパクトな「分かち書き」ツール(品詞推定はない)。これをRで実装したのが{rTinySegmenter}です。
yamano357/rTinySegmenter · GitHub
library(dplyr) library(rTinySegmenter) # Original Model segmentr <- tiny_segmenter$new(text = "私の名前は中野です") > segmentr$tokenize() [1] "私 | の | 名前 | は | 中野 | です" > names(x = segmentr$model$TEMPLATE) [1] "BC1" "BC2" "BC3" "BP1" "BP2" "BQ1" "BQ2" "BQ3" "BQ4" "BW1" "BW2" "BW3" "TC1" "TC2" "TC3" "TC4" "TQ1" "TQ2" "TQ3" "TQ4" "TW1" "TW2" "TW3" [24] "TW4" "UC1" "UC2" "UC3" "UC4" "UC5" "UC6" "UP1" "UP2" "UP3" "UQ1" "UQ2" "UQ3" "UW1" "UW2" "UW3" "UW4" "UW5" "UW6" > segmentr$model$TEMPLATE[[names(x = segmentr$model$TEMPLATE)[1]]] $PATTERN $PATTERN$P [1] "" $PATTERN$W [1] "" $PATTERN$C [1] 2 3 $SCORE $SCORE$HH [1] 6 $SCORE$II [1] 2461 $SCORE$KH [1] 406 $SCORE$OH [1] -1378 # feature > segmentr$model_attr$unit %>% + as.data.frame() BC1 BC2 BC3 BP1 BP2 BQ1 BQ2 BQ3 BQ4 BW1 BW2 BW3 TC1 TC2 TC3 TC4 TQ1 TQ2 TQ3 1 OH HI IH UU UU UOH UHI UOH UHI B1私 私の の名 OOH OHI HIH IHH UOOH UOHI UOOH 2 HI IH HH UU UB UHI UIH BHI BIH 私の の名 名前 OHI HIH IHH HHI UOHI UHIH BOHI 3 IH HH HI UB BB BIH BHH BIH BHH の名 名前 前は HIH IHH HHI HIH BHIH BIHH BHIH 4 HH HI IH BB BO BHH BHI OHH OHI 名前 前は は中 IHH HHI HIH IHH BIHH BHHI OIHH 5 HI IH HH BO OB OHI OIH BHI BIH 前は は中 中野 HHI HIH IHH HHI OHHI OHIH BHHI 6 IH HH HI OB BB BIH BHH BIH BHH は中 中野 野で HIH IHH HHI HII BHIH BIHH BHIH 7 HH HI II BB BO BHH BHI OHH OHI 中野 野で です IHH HHI HII IIO BIHH BHHI OIHH 8 HI II IO BO OB OHI OII BHI BII 野で です すE1 HHI HII IIO IOO OHHI OHII BHHI TQ4 TW1 TW2 TW3 TW4 UC1 UC2 UC3 UC4 UC5 UC6 UP1 UP2 UP3 UQ1 UQ2 UQ3 1 UOHI B2B1私 B1私の 私の名 の名前 O O H I H H U U U UO UO UH 2 BHIH B1私の 私の名 の名前 名前は O H I H H I U U B UO UH BI 3 BIHH 私の名 の名前 名前は 前は中 H I H H I H U B B UH BI BH 4 OHHI の名前 名前は 前は中 は中野 I H H I H H B B O BI BH OH 5 BHIH 名前は 前は中 は中野 中野で H H I H H I B O B BH OH BI 6 BIHH 前は中 は中野 中野で 野です H I H H I I O B B OH BI BH 7 OHHI は中野 中野で 野です ですE1 I H H I I O B B O BI BH OH 8 BHII 中野で 野です ですE1 すE1E2 H H I I O O B O B BH OH BI UW1 UW2 UW3 UW4 UW5 UW6 1 B2 B1 私 の 名 前 2 B1 私 の 名 前 は 3 私 の 名 前 は 中 4 の 名 前 は 中 野 5 名 前 は 中 野 で 6 前 は 中 野 で す 7 は 中 野 で す E1 8 中 野 で す E1 E2 # feature valus > segmentr$model_attr$score %>% + as.data.frame() BC1 BC2 BC3 BP1 BP2 BQ1 BQ2 BQ3 BQ4 BW1 BW2 BW3 TC1 TC2 TC3 1 -1378 0 -301 0 0 0 -1146 0 0 0 0 0 0 0 0 2 0 -1184 996 0 0 0 0 2664 3761 0 0 0 0 0 128 3 0 -4070 626 352 0 0 118 0 -3895 0 0 0 0 0 -341 4 6 0 -301 295 60 1150 -1159 2174 0 0 0 0 0 0 0 5 0 -1184 996 0 0 451 153 2664 3761 0 0 0 0 0 128 6 0 -4070 626 304 0 0 118 0 -3895 0 0 0 0 0 -341 7 6 0 0 295 60 1150 -1159 2174 0 0 0 1437 0 0 -1088 8 0 -1332 0 0 0 451 0 2664 -4654 0 -4761 0 0 -1023 0 TC4 TQ1 TQ2 TQ3 TQ4 TW1 TW2 TW3 TW4 UC1 UC2 UC3 UC4 UC5 UC6 UP1 UP2 1 695 0 0 0 0 0 0 0 0 -505 646 0 -1032 313 -506 0 0 2 1344 0 0 0 0 0 0 0 0 -505 1059 2311 1809 313 -253 0 0 3 804 -132 -1401 222 0 0 0 0 0 0 409 0 1809 -1238 -506 0 69 4 695 60 0 623 2446 0 0 0 0 0 1059 0 -1032 313 -506 0 69 5 1344 0 0 0 0 0 0 0 0 0 1059 2311 1809 313 -253 0 935 6 679 -132 -1401 222 0 0 0 0 0 0 409 0 1809 -1238 -253 -214 69 7 656 60 0 623 2446 0 0 0 0 0 1059 0 -1032 -1238 -387 0 69 8 54 0 0 0 -966 0 0 0 0 0 1059 2311 -1032 -831 -387 0 935 UP3 UQ1 UQ2 UQ3 UW1 UW2 UW3 UW4 UW5 UW6 1 0 0 0 0 0 0 4231 7396 0 302 2 189 0 0 1913 0 0 4056 0 0 -236 3 189 0 113 42 0 130 0 1623 -578 201 4 0 -12 216 0 -185 0 2286 8578 -871 0 5 189 21 0 1913 0 0 4555 2210 0 101 6 189 -95 113 42 0 -409 653 -1100 -850 383 7 0 -12 216 0 -847 -968 0 7410 -852 306 8 189 21 0 1913 0 0 2318 -731 0 0 # Custom Model small_segmentr <- tiny_segmenter$new( text = "私の名前は中野です", model_file = system.file(package = "rTinySegmenter", "extdata", "small-model.json") ) > small_segmentr$tokenize() [1] "私 | の | 名前 | は | 中野 | で | す" > names(x = small_segmentr$model$TEMPLATE) [1] "BC1" "BC2" "BP1" "BP2" "BQ1" "BQ2" "BQ3" "BW1" "BW2" "TC1" "TC2" "TC3" "TQ1" "TQ2" "TQ3" "TW1" "TW2" "TW3" [19] "UC1" "UC2" "UC3" "UP1" "UP2" "UP3" "UQ1" "UQ2" "UQ3" "UW1" "UW2" "UW3" # feature > small_segmentr$model_attr$unit %>% + as.data.frame() BC1 BC2 BP1 BP2 BQ1 BQ2 BQ3 BW1 BW2 TC1 TC2 TC3 TQ1 TQ2 TQ3 TW1 TW2 TW3 UC1 1 OH HI UU UU UOH UHI UOH B1私 私の OOH OHI HIH UOOH UOHI UOOH B2B1私 B1私の 私の名 O 2 HI IH UU UB UHI UIH BHI 私の の名 OHI HIH IHH UOHI UHIH BOHI B1私の 私の名 の名前 O 3 IH HH UB BB BIH BHH BIH の名 名前 HIH IHH HHI BHIH BIHH BHIH 私の名 の名前 名前は H 4 HH HI BB BO BHH BHI OHH 名前 前は IHH HHI HIH BIHH BHHI OIHH の名前 名前は 前は中 I 5 HI IH BO OB OHI OIH BHI 前は は中 HHI HIH IHH OHHI OHIH BHHI 名前は 前は中 は中野 H 6 IH HH OB BB BIH BHH BIH は中 中野 HIH IHH HHI BHIH BIHH BHIH 前は中 は中野 中野で H 7 HH HI BB BO BHH BHI OHH 中野 野で IHH HHI HII BIHH BHHI OIHH は中野 中野で 野です I 8 HI II BO OB OHI OII BHI 野で です HHI HII IIO OHHI OHII BHHI 中野で 野です ですE1 H UC2 UC3 UP1 UP2 UP3 UQ1 UQ2 UQ3 UW1 UW2 UW3 1 O H U U U UO UO UH B2 B1 私 2 H I U U B UO UH BI B1 私 の 3 I H U B B UH BI BH 私 の 名 4 H H B B O BI BH OH の 名 前 5 H I B O B BH OH BI 名 前 は 6 I H O B B OH BI BH 前 は 中 7 H H B B O BI BH OH は 中 野 8 H I B O B BH OH BI 中 野 で
まとめ
Japan.R 2015にて発表した資料から、各種言語で実装された形態素解析器をRから呼び出すコードと、TinySegmenterのR実装である{rTinySegmenter}を紹介したときのコードを記載しました。以前に作った日本語言語処理パッケージではMeCabを呼び出すとメモリーアロケーションを引き起こさせてしまっていたので、修正に合わせてKyTeaも追加したいと思います。
なお、{rTinySegmenter}はJSON形式のファイルとテンプレートに従った命名とパターン作りによってスコア定義で可能ですが、一部バグ(テンプレートが反映できないケースがある)が残っているので修正します。また、処理速度が遅すぎて使い物にならないので、資料に出てきたようなデータ構造を用いるように改良してきます。
参考
その他
LT資料の近況に記載がありますが、Japan.Rが開催された日が前職最終出社翌日でした。欲しいものリストを公開したところ、 皆さんからお祝いをいただきました。ありがとうございます。
いただいた書籍については、また後日記事に書かせていただきます。繰り返しになりますが、ありがとうございました。
実行環境
> 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.486) language (EN) collate ja_JP.UTF-8 tz Asia/Tokyo Packages -------------------------------------------------------------------------------------- package * version date source assertthat 0.1 2013-12-06 CRAN (R 3.2.0) codetools 0.2-14 2015-07-15 CRAN (R 3.2.2) curl 0.9.3 2015-08-25 CRAN (R 3.2.0) DBI 0.3.1 2014-09-24 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) dplyr * 0.4.2.9002 2015-07-25 Github (hadley/dplyr@75e8303) foreach 1.4.2 2014-04-11 CRAN (R 3.2.0) git2r 0.10.1 2015-05-07 CRAN (R 3.2.0) iterators 1.0.7 2014-04-11 CRAN (R 3.2.0) jsonlite 0.9.16 2015-04-11 CRAN (R 3.2.0) magrittr 1.5 2014-11-22 CRAN (R 3.2.0) memoise 0.2.1 2014-04-22 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) rJava * 0.9-7 2015-07-25 local RMeCab * 0.99991 2015-08-20 local rstudioapi 0.3.1 2015-04-07 CRAN (R 3.2.0) rTinySegmenter * 0.1 2015-11-29 local rversions 1.0.1 2015-06-06 CRAN (R 3.2.0) stringi 0.5-5 2015-06-29 CRAN (R 3.2.0) stringr 1.0.0.9000 2015-07-25 Github (hadley/stringr@380c88f) tidyr 0.2.0.9000 2015-07-25 Github (hadley/tidyr@0dc87b2) xml2 0.1.1 2015-06-02 CRAN (R 3.2.0)