- 投稿日:2021-01-20T20:25:06+09:00
RcppKagome - Rcpp経由でGoのライブラリを呼んで形態素解析するRパッケージ
これは何?
Rで形態素解析するためのパッケージです。Pure Goで辞書同梱な形態素解析器として知られるikawaha/kagomeをラップしています。
使い方
インストール
ソースからビルドします。makeとGCCとGoが必要です。
remotes::install_github("paithiov909/RcppKagome")形態素解析
character vectorを渡せます。戻り値はリストです。
res <- RcppKagome::kagome("にわにはにわにわとりがいる") str(res) #> List of 1 #> $ :List of 6 #> ..$ 0:List of 5 #> .. ..$ Id : int 53040 #> .. ..$ Start : int 0 #> .. ..$ End : int 1 #> .. ..$ Surface: chr "に" #> .. ..$ Feature: chr [1:9] "助詞" "格助詞" "一般" "*" ... #> ..$ 1:List of 5 #> .. ..$ Id : int 80172 #> .. ..$ Start : int 1 #> .. ..$ End : int 3 #> .. ..$ Surface: chr "わに" #> .. ..$ Feature: chr [1:9] "名詞" "一般" "*" "*" ... #> ..$ 2:List of 5 #> .. ..$ Id : int 58916 #> .. ..$ Start : int 3 #> .. ..$ End : int 6 #> .. ..$ Surface: chr "はにわ" #> .. ..$ Feature: chr [1:9] "名詞" "一般" "*" "*" ... #> ..$ 3:List of 5 #> .. ..$ Id : int 53999 #> .. ..$ Start : int 6 #> .. ..$ End : int 10 #> .. ..$ Surface: chr "にわとり" #> .. ..$ Feature: chr [1:9] "名詞" "一般" "*" "*" ... #> ..$ 4:List of 5 #> .. ..$ Id : int 19676 #> .. ..$ Start : int 10 #> .. ..$ End : int 11 #> .. ..$ Surface: chr "が" #> .. ..$ Feature: chr [1:9] "助詞" "格助詞" "一般" "*" ... #> ..$ 5:List of 5 #> .. ..$ Id : int 6652 #> .. ..$ Start : int 11 #> .. ..$ End : int 13 #> .. ..$ Surface: chr "いる" #> .. ..$ Feature: chr [1:9] "動詞" "自立" "*" "*" ...結果をデータフレームに整形できます。
res <- RcppKagome::kagome(c("庭に埴輪に輪と李がいる", "庭には二羽鶏がいる")) res <- RcppKagome::prettify(res) print(res) #> Sid Surface POS1 POS2 POS3 POS4 X5StageUse1 X5StageUse2 Original Yomi1 #> 1 1 庭 名詞 一般 <NA> <NA> <NA> <NA> 庭 ニワ #> 2 1 に 助詞 格助詞 一般 <NA> <NA> <NA> に ニ #> 3 1 埴輪 名詞 一般 <NA> <NA> <NA> <NA> 埴輪 ハニワ #> 4 1 に 助詞 格助詞 一般 <NA> <NA> <NA> に ニ #> 5 1 輪 名詞 一般 <NA> <NA> <NA> <NA> 輪 ワ #> 6 1 と 助詞 並立助詞 <NA> <NA> <NA> <NA> と ト #> 7 1 李 名詞 固有名詞 人名 姓 <NA> <NA> 李 リ #> 8 1 が 助詞 格助詞 一般 <NA> <NA> <NA> が ガ #> 9 1 いる 動詞 自立 <NA> <NA> 一段 基本形 いる イル #> 10 2 庭 名詞 一般 <NA> <NA> <NA> <NA> 庭 ニワ #> 11 2 に 助詞 格助詞 一般 <NA> <NA> <NA> に ニ #> 12 2 は 助詞 係助詞 <NA> <NA> <NA> <NA> は ハ #> 13 2 二 名詞 数 <NA> <NA> <NA> <NA> 二 ニ #> 14 2 羽 名詞 接尾 助数詞 <NA> <NA> <NA> 羽 ワ #> 15 2 鶏 名詞 一般 <NA> <NA> <NA> <NA> 鶏 ニワトリ #> 16 2 が 助詞 格助詞 一般 <NA> <NA> <NA> が ガ #> 17 2 いる 動詞 自立 <NA> <NA> 一段 基本形 いる イル #> Yomi2 #> 1 ニワ #> 2 ニ #> 3 ハニワ #> 4 ニ #> 5 ワ #> 6 ト #> 7 リ #> 8 ガ #> 9 イル #> 10 ニワ #> 11 ニ #> 12 ワ #> 13 ニ #> 14 ワ #> 15 ニワトリ #> 16 ガ #> 17 イル整形されたデータフレームは次のカラムからなります。
- Sid: 文番号(sentence index)
- Surface: 表層形
- POS1~POS4: 品詞, 品詞細分類1, 品詞細分類2, 品詞細分類3
- X5StageUse1: 活用型(ex. 五段, 下二段…)
- X5StageUse2: 活用形(ex. 連用形, 終止形…)
- Original: 原形(lemmatised form)
- Yomi1: 読み(readings)
- Yomi2: 発音(pronunciation)
このうちSurface列だけを半角スペースでcollapseして返す(分かち書きにする)ことができます。
RcppKagome::pack(res) #> Sid Text #> 1 1 庭 に 埴輪 に 輪 と 李 が いる #> 2 2 庭 に は 二 羽 鶏 が いる以下の記事のなかで実際に使用しています。この記事では分かち書きにした文書をquantedaのコーパスとして持っています。
ベンチマーク
RMeCabと比較してとくに遅いということはないはずです。
str <- "キャピキャピ音が高くなってきたら、ほんとに出してくれの合図です! しっかりここではコミュニケーションとってください" tm <- microbenchmark::microbenchmark( RMeCab = RMeCab::RMeCabC(str), RcppKagome = RcppKagome::kagome(str), times = 500L ) summary(tm) #> expr min lq mean median uq max neval #> 1 RMeCab 1.9835 2.50085 3.339689 2.77485 3.1911 83.4071 500 #> 2 RcppKagome 2.1079 2.53700 3.246598 2.76890 3.2572 18.3404 500ggplot2::autoplot(tm) #> Coordinate system already present. Adding new coordinate system, which will replace the existing one.RとGoの連携について
日本語情報としては以下の記事があります。
この記事はもともとRomain Francois氏(RcppとかrJavaとかの開発にかかわっているスゴい人らしい)が書いたブログ記事を参考にしているものです。
Goには
cgo
というコマンドが用意されていて、Goで書かれたコードをC言語から利用するためのライブラリにすることができます。この機能を利用して生成したC言語向けのライブラリをRパッケージから呼び出すことで、いちおうはGoの資産をRから利用することができます。本来、Cなどで書かれた関数をRパッケージで直接呼ぶためには
.Call
を使って呼べる状態にするために関数のregistration
という操作が必要になります。この手間を省略するために、RcppKagomeではcgoで生成したライブラリをC++から利用するラッパーを書いて、それをRcppでエクスポートしています。また、より便利に扱うためにはGoとのあいだに型マッピングを定義するのが望ましいのでしょうが、RcppKagomeではその点については深入りせず、文字列だけを受け渡しするようにしています。参考までに、Goとのあいだに型マッピングを定義しているほかの例を挙げておきます。
セッション情報
sessioninfo::session_info() #> - Session info ------------------------------------------------------------------------ #> setting value #> version R version 4.0.3 (2020-10-10) #> os Windows 10 x64 #> system x86_64, mingw32 #> ui RStudio #> language (EN) #> collate Japanese_Japan.932 #> ctype Japanese_Japan.932 #> tz Asia/Tokyo #> date 2021-01-20 #> #> - Packages ---------------------------------------------------------------------------- #> package * version date lib source #> assertthat 0.2.1 2019-03-21 [1] CRAN (R 4.0.2) #> backports 1.2.1 2020-12-09 [1] CRAN (R 4.0.3) #> cli 2.2.0 2020-11-20 [1] CRAN (R 4.0.3) #> codetools 0.2-16 2018-12-24 [2] CRAN (R 4.0.3) #> colorspace 2.0-0 2020-11-11 [1] CRAN (R 4.0.3) #> crayon 1.3.4 2017-09-16 [1] CRAN (R 4.0.2) #> DBI 1.1.1 2021-01-15 [1] CRAN (R 4.0.3) #> digest 0.6.27 2020-10-24 [1] CRAN (R 4.0.3) #> dplyr 1.0.3 2021-01-15 [1] CRAN (R 4.0.3) #> ellipsis 0.3.1 2020-05-15 [1] CRAN (R 4.0.2) #> evaluate 0.14 2019-05-28 [1] CRAN (R 4.0.2) #> fansi 0.4.2 2021-01-15 [1] CRAN (R 4.0.3) #> farver 2.0.3 2020-01-16 [1] CRAN (R 4.0.2) #> furrr 0.2.1 2020-10-21 [1] CRAN (R 4.0.2) #> future 1.21.0 2020-12-10 [1] CRAN (R 4.0.3) #> generics 0.1.0 2020-10-31 [1] CRAN (R 4.0.3) #> ggplot2 3.3.3 2020-12-30 [1] CRAN (R 4.0.3) #> globals 0.14.0 2020-11-22 [1] CRAN (R 4.0.3) #> glue 1.4.2 2020-08-27 [1] CRAN (R 4.0.2) #> gtable 0.3.0 2019-03-25 [1] CRAN (R 4.0.2) #> htmltools 0.5.1 2021-01-12 [1] CRAN (R 4.0.3) #> jsonlite 1.7.2 2020-12-09 [1] CRAN (R 4.0.3) #> knitr 1.30 2020-09-22 [1] CRAN (R 4.0.2) #> lifecycle 0.2.0 2020-03-06 [1] CRAN (R 4.0.2) #> listenv 0.8.0 2019-12-05 [1] CRAN (R 4.0.2) #> magrittr 2.0.1 2020-11-17 [1] CRAN (R 4.0.3) #> microbenchmark 1.4-7 2019-09-24 [1] CRAN (R 4.0.2) #> munsell 0.5.0 2018-06-12 [1] CRAN (R 4.0.2) #> parallelly 1.23.0 2021-01-04 [1] CRAN (R 4.0.3) #> pillar 1.4.7 2020-11-20 [1] CRAN (R 4.0.3) #> pkgconfig 2.0.3 2019-09-22 [1] CRAN (R 4.0.2) #> purrr * 0.3.4 2020-04-17 [1] CRAN (R 4.0.2) #> R.cache 0.14.0 2019-12-06 [1] CRAN (R 4.0.2) #> R.methodsS3 1.8.1 2020-08-26 [1] CRAN (R 4.0.2) #> R.oo 1.24.0 2020-08-26 [1] CRAN (R 4.0.2) #> R.utils 2.10.1 2020-08-26 [1] CRAN (R 4.0.2) #> R6 2.5.0 2020-10-28 [1] CRAN (R 4.0.3) #> Rcpp 1.0.6 2021-01-15 [1] CRAN (R 4.0.3) #> RcppKagome * 0.0.0.500 2021-01-20 [1] local #> rlang 0.4.10 2020-12-30 [1] CRAN (R 4.0.3) #> rmarkdown 2.6 2020-12-14 [1] CRAN (R 4.0.3) #> RMeCab * 1.05 2020-04-28 [1] local #> scales 1.1.1 2020-05-11 [1] CRAN (R 4.0.2) #> sessioninfo 1.1.1 2018-11-05 [1] CRAN (R 4.0.2) #> stringi 1.5.3 2020-09-09 [1] CRAN (R 4.0.2) #> stringr 1.4.0 2019-02-10 [1] CRAN (R 4.0.2) #> styler 1.3.2 2020-02-23 [1] CRAN (R 4.0.2) #> tibble 3.0.5 2021-01-15 [1] CRAN (R 4.0.3) #> tidyselect 1.1.0 2020-05-11 [1] CRAN (R 4.0.2) #> vctrs 0.3.6 2020-12-17 [1] CRAN (R 4.0.3) #> withr 2.4.0 2021-01-16 [1] CRAN (R 4.0.3) #> xfun 0.20 2021-01-06 [1] CRAN (R 4.0.3) #> yaml 2.2.1 2020-02-01 [1] CRAN (R 4.0.0) #> #> [1] C:/Users/user/Documents/R/win-library/4.0 #> [2] C:/Program Files/R/R-4.0.3/library
- 投稿日:2021-01-20T16:53:32+09:00
【自己学習用】はじめてのGo2
基本的な文法の振り返り
mainパッケージ
プログラムをコンパイルして実行すると,まずmainパッケージの中にあるmain()関数が実行される。
package main func main() { }インポート
インポートしたパッケージ内へは,パッケージ名にドットを付けてアクセスできる。
package main import ( "fmt" "github.com/wdpress/gosample" "strings" ) func main() { fmt.Println("hello world") }オプションの指定
任意の名前(上記ではf)を指定した場合はfで使用可能。
_を付けた場合は使用しないの意。
.を付けた場合は,使用時にパッケージ名が省略可能。package main import ( f "fmt" _ "github.com/wdpress/gosample" . "strings" ) func main() { // fmt.Println()がf.Println()になり // strings.ToUpper()がToUpper()なっている f.Println(ToUpper("hello world")) }変数
//var message string = "hello world" //var foo, bar, buz string = "foo", "bar", "buz" func main() { // どちらの書き方も同じ意味 // var message string = "hello world" message := "hello world" fmt.Println(message) const Hello string = "hello" Hello = "bye" // cannot assign to Hello }変数の宣言はvarから。JSと似ている。
複数の宣言ができるところも似てる。
varと変数の型は省略可能。
型は推論。ここは似なくてもいいのに・・・if
func main() { a, b := 10, 100 if a > b { fmt.Println("a is larger than b") } else if a < b { fmt.Println("a is smaller than b") } else { fmt.Println("a equals b") } } if n == 10 fmt.Println(n) //これはエラー n == 10 ? fmt.Println(n): fmt.Println(0) //これもエラーifに丸括弧は不要。個人的には欲しい。
波括弧の省略はエラー。
ifの省略もエラー。for
func main() { for i := 0; i < 10; i++ { fmt.Println(i) } for { //無限ループ doSomething() } }forも丸括弧は不要。
whileはなし。switch
func main() { n := 3 switch n { case 3: n = n - 1 fallthrough case 2: n = n - 1 fallthrough case 1: n = n - 1 fmt.Println(n) // 0 } }goのswitchはcaseが終わったら抜けるのでbreakを書く必要はない。
続けたい場合はfallthrough。関数
func sum(i, j int) { // func sum(i int, j int) と同じ fmt.Println(i + j) }引数の型が同じなら最後にまとめることができる。
func swap(i, j int) (int, int) { return j, i } func main() { x, y := 3, 4 x, y = swap(x, y) fmt.Println(x, y) // 4, 3 x = swap(x, y) // コンパイルエラー x, _ = swap(x, y) // 第二戻り値を無視 fmt.Println(x) // 3 swap(x, y) // コンパイル,実行ともに可能 }関数は複数の値を返すことができる。
戻り値の数があってないとコンパイルエラーだが
_とすれば明示的に無視することができる。エラーを返す関数
func main() { file, err := os.Open("hello.go") if err != nil { // エラー処理 // returnなどで処理を別の場所に抜ける } // fileを用いた処理 }エラーでない場合、nil。
自作のエラーはerrorsパッケージを使う。
エラーを最後にする慣習があるため、自作APIもそれに従う。package main import ( "errors" "fmt" "log" ) func div(i, j int) (int, error) { if j == 0 { // 自作のエラーを返す return 0, errors.New("divied by zero") } return i / j, nil } func main() { n, err := div(10, 0) if err != nil { // エラーを出力しプログラムを終了する log.Fatal(err) } fmt.Println(n) }名前付き戻り値
func div(i, j int) (result int, err error) { if j == 0 { err = errors.New("divied by zero") return // return 0, errと同じ } result = i / j return // return result, nilと同じ }戻り値に名前を付けることができ、ゼロ値で初期化された変数として扱われる。
この場合、returnのあとに返す値を明示する必要がない。関数リテラル
無名関数が作れる。
すぐに実行する関数を作る場合は以下。func main() { func(i, j int) { fmt.Println(i + j) }(2, 4) }関数を変数に代入可能。
これもJSと同じ。var sum func(i, j int) = func(i, j int) { fmt.Println(i + j) } func main() { sum(2, 4) }配列
goの配列は固定長。
関数に配列を渡す場合は値渡しとなる。func fn(arr [4]string) { arr[0] = "x" fmt.Println(arr) // [x b c d] } func main() { arr := [4]string{"a", "b", "c", "d"} fn(arr) fmt.Println(arr) // [a b c d] }スライス
スライス=可変長配列。
for文の中でrangeを用いると,添字と値の両方が取得できる。s1 := []string{"a", "b"} s2 := []string{"c", "d"} s1 = append(s1, s2...) // s1にs2を追加 fmt.Println(s1) // [a b c d] var arr [4]string arr[0] = "a" arr[1] = "b" arr[2] = "c" arr[3] = "d" for i, s := range arr { // i = 添字, s = 値 fmt.Println(i, s) }始点と終点をコロンで挟んで指定すると,その範囲の値を切り出すことができる。
始点,終点を省略した場合,それぞれ先頭,末尾となる。s := []int{0, 1, 2, 3, 4, 5} fmt.Println(s[2:4]) // [2 3] fmt.Println(s[0:len(s)]) // [0 1 2 3 4 5] fmt.Println(s[:3]) // [0 1 2] fmt.Println(s[3:]) // [3 4 5] fmt.Println(s[:]) // [0 1 2 3 4 5]マップ
//var month map[int]string = map[int]string{} month := map[int]string{ 1: "January", 2: "February", } fmt.Println(month) // map[1:January 2:February]マップからキーと取り出す場合。
キーがあるかを調べる場合は二つ目の引数で判断できる。(boolで返却)jan := month[1] fmt.Println(jan) // January _, ok := month[1] if ok { // データがあった場合 }スライス同様,rangeを使用可能。
defer
ファイル操作などを行う場合,使用後のファイルは必ず閉じる必要がある。
途中で抜けたりして閉じられない可能性が出てくると困る処理はdeferを付ける。
下記ではmain()を抜ける直前に必ず実行されるようになる。func main() { file, err := os.Open("./error.go") if err != nil { // エラー処理 } // 関数を抜ける前に必ず実行される defer file.Close() // 正常処理 }パニック
戻り値で表現できないエラーはパニックという方法でエラーが発生する。
func main() { defer func() { err := recover() if err != nil { // runtime error: index out of range log.Fatal(err) } }() a := []int{1, 2, 3} fmt.Println(a[10]) // パニックが発生 }このパニックで発生したエラーはrecover()という組込み関数で取得し,そこでエラー処理を実施できる。
recover()をdeferの中に書くことで,パニックで発生したエラーの処理を実施してから,関数を抜けることができる。パニックは組込み関数panic()を用いて自分で発生させることもできる。
a := []int{1, 2, 3} for i := 0; i < 10; i++ { if i >= len(a) { panic(errors.New("index out of range")) } fmt.Println(a[i]) }ただしパニックを用いるのは,エラーを戻り値として表現できない場合や,回復が不可能なシステムエラー,やむを得ず大域脱出が必要な場合など。
基本は戻り値で表現する。
- 投稿日:2021-01-20T11:45:17+09:00
Goの複数バージョン管理をanyenv+goenvで行う
Go言語で開発している際にローカルで複数バージョン使い分けしたい時がしばしば発生するので、その対策としてgoenvおよびanyenvを使ってバージョンの切り替えを容易にする方法を紹介します。
goenvとは
https://github.com/syndbg/goenv
pyenvやrbenvをモデルに作られたGoのバージョン管理ツールです。
これを使うことによってバージョンの切り替え、Goの開発プロジェクトごとにバージョンを変更、環境変数でGoのバージョンの切り替えなどを行うことができるようになります。anyenvとは
https://github.com/anyenv/anyenv
〜env系のツールを管理するツールです。
上述のgoenvやpyenv,rbenvに加え、nodenvやtfenvなど様々な言語のバージョン管理ツールを制御することができます。
今回の記事ではGoだけを取り扱うのでanyenvはなくても大丈夫なのですが、今後他の言語のバージョン管理する可能性あることを考えて一緒に紹介しておきます。インストール
anyenvのインストール
Homebrewをインストール済であれば、下記のコマンドでインストールします。
% brew install anyenv使用中のシェルにanyenvのセットアップを行います。
% anyenv initセットアップを適用する為にシェルを起動しなおします。(一度閉じて新しいターミナルを開くだけで大丈夫です)
goenvのインストール
anyenvを使用してgoenvをインストールし、シェルを起動しなおします。
% anyenv install goenv % exec $SHELL -lGoのインストール
インストールできるバージョンを確認します。
% goenv install -l Available versions: 1.2.2 . . . 1.15.6 1.16beta1目的のバージョンを指定してインストールします。
% goenv install 1.15.6インストールされているバージョン一覧を表示するには下記のコマンドを実行します。
% goenv versionsベースとなるバージョン指定
% goenv global 1.15.6 % go version go version go1.15.6 darwin/amd64これで1.15.6がデフォルトのgoのバージョンとなります。
ディレクトリでバージョン指定
~/sample
ディレクトリではGo v1.14.13を使いたい場合% cd ~/sample % goenv local 1.14.13 % go version go version go1.14.13 darwin/amd64goenvのアップデート
新しいGoのバージョンがリリースされた際に
goenv install -l
で表示されない場合はgoenvのアップデートが必要です。% anyenv install goenv anyenv: /Users/xxxxx/.anyenv/envs/goenv already exists Reinstallation keeps versions directories continue with installation? (y/N) y ← yを入力してエンターシェルを再起動したらgoenvで利用可能なGoのバージョンが最新化されているはずです。