20210120のGoに関する記事は3件です。

RcppKagome - Rcpp経由でGoのライブラリを呼んで形態素解析するRパッケージ

paithiov909/RcppKagome - GitHub

これは何?

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   500
ggplot2::autoplot(tm)
#> Coordinate system already present. Adding new coordinate system, which will replace the existing one.

benchmark_2-1.png

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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【自己学習用】はじめての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の省略もエラー。:thumbsup:

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])
}

ただしパニックを用いるのは,エラーを戻り値として表現できない場合や,回復が不可能なシステムエラー,やむを得ず大域脱出が必要な場合など。
基本は戻り値で表現する。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 -l

Goのインストール

インストールできるバージョンを確認します。

% 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/amd64

goenvのアップデート

新しい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のバージョンが最新化されているはずです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む