はじめに
形態素解析を利用して単語(名詞のみ)の出現数をカウントする簡単なWebアプリを作成してみたので、その作成手順を紹介していきます。
同様のWebアプリをPython+MeCabで実装したことがあったので、今回はこれまで触れたことがなかったGo言語で挑戦してみました。
開発環境
開発環境は以下の通りです。
- OS: macOS Sierra 10.12.6
- Homebrew: 1.4.1
- goenv: 1.0.0
- Go言語: 1.9.2
- kagome
- gin
Go言語のインストール
まずはGo言語をインストールするところからはじめます。
こちらの記事
を参考に、goenvを利用してGo言語をインストールしていきます。
まずはHomebrewでgoenvをインストールします。
$ brew install goenv
goenvを利用するため、~/.zshrc
の末尾に以下の設定を追加します。(bashを利用している場合は~/.bashrc
に読み替えてください)
+
+export PATH="$HOME/.goenv/bin:$PATH"
+eval "$(goenv init -)"
設定を追加したらsourceコマンドを実行して変更内容を反映させます。
$ source ~/.zshrc
その後、goenvを利用してGo言語(バージョン1.9.2)をインストールしていきます。
$ goenv install 1.9.2
goenvでインストールしたGo言語を使用するためには、以下のコマンドを実行して使用するGo言語のバージョンを設定する必要があります。
$ goenv global 1.9.2
go version
を実行して、インストールしたバージョン(1.9.2)が表示されればOKです。
$ go version
go version go1.9.2 darwin/amd64
Go言語でHello worldを試す
Go言語のインストールが完了したところで、まずはmain.go
というファイルを作成してHello worldを試していきます。
package main
import "fmt"
func main() {
fmt.Printf("Hello, world!\n")
}
main
は特別なパッケージ名で、main()
関数を定義することでエントリポイントとして扱われるようです。
$ go run main.go
Hello, world!
また、Go言語はコンパイル言語でありソースコードをコンパイルして実行可能ファイルを生成することもできるので、こちらも試してみます。
# main.goをコンパイル
$ go build main.go
# 実行可能ファイルが生成されたことを確認
$ ls
main main.go
# 実行可能ファイルを実行
$ ./main
Hello, world!
kagomeのインストール
次に、こちらの記事を参考にしてkagomeというGo言語製の日本語形態素解析器をインストールしていきます。
$ go get github.com/ikawaha/kagome/...
上記コマンド実行後、環境変数$GOPATH
で設定しているディレクトリ以下にkagomeがインストールされます。($GOPATH
を設定していない場合、デフォルト値として$HOME/go
を使用するようです)
kagomeで形態素解析を実行
kagomeをコマンドとして実行
まずはターミナル上でコマンドとしてkagomeを実行してみます。
$ ~/go/bin/kagome
すもももももももものうち
すもも 名詞,一般,*,*,*,*,すもも,スモモ,スモモ
も 助詞,係助詞,*,*,*,*,も,モ,モ
もも 名詞,一般,*,*,*,*,もも,モモ,モモ
も 助詞,係助詞,*,*,*,*,も,モ,モ
もも 名詞,一般,*,*,*,*,もも,モモ,モモ
の 助詞,連体化,*,*,*,*,の,ノ,ノ
うち 名詞,非自立,副詞可能,*,*,*,うち,ウチ,ウチ
EOS
kagomeをライブラリとして使用
ほぼこちらの記事に記載されているサンプルコードのコピペとなりますが、main.go
を修正してkagomeを利用した形態素解析を実行できるように修正していきます。
tokenizer
インスタンス生成周りのコードについてはsrc/github.com/ikawaha/kagome/cmd/kagome/tokenize/cmd.go
のcommand()
関数を参考に実装しました。
package main
-import "fmt"
+import (
+ "fmt"
+ "strings"
+ "github.com/ikawaha/kagome/tokenizer"
+)
func main() {
- fmt.Println("Hello, world!\n")
+ dic := tokenizer.SysDicSimple()
+ t := tokenizer.NewWithDic(dic)
+ tokens := t.Tokenize("すもももももももものうち")
+ for _, token := range tokens {
+ features := strings.Join(token.Features(), ",")
+ fmt.Printf("%s\t%v\n", token.Surface, features)
+ }
}
実行結果は以下の通りです。
$ go run main.go
BOS
すもも 名詞,一般,*,*,*,*
も 助詞,係助詞,*,*,*,*
もも 名詞,一般,*,*,*,*
も 助詞,係助詞,*,*,*,*
もも 名詞,一般,*,*,*,*
の 助詞,連体化,*,*,*,*
うち 名詞,非自立,副詞可能,*,*,*
EOS
これで形態素解析が実行できるようになりました!
形態素解析の実行結果から名詞の単語のみ出現回数をカウント
形態素解析の結果から名詞の単語のみを抽出し、各単語の出現回数をカウントできるように修正します。
import (
"fmt"
- "strings"
"github.com/ikawaha/kagome/tokenizer"
)
...
dic := tokenizer.SysDicSimple()
t := tokenizer.NewWithDic(dic)
tokens := t.Tokenize("すもももももももものうち")
+ words := map[string]int{}
for _, token := range tokens {
- features := strings.Join(token.Features(), ",")
- fmt.Printf("%s\t%v\n", token.Surface, features)
+ features := token.Features()
+ // 名詞以外ならスキップ
+ if len(features) == 0 || features[0] != "名詞" {
+ continue
+ }
+
+ // マップに既にキーが存在する場合はインクリメント、存在しない場合は1で初期化
+ _, ok := words[token.Surface]
+ if ok {
+ words[token.Surface]++
+ } else {
+ words[token.Surface] = 1
+ }
+ }
+
+ for word, count := range words {
+ fmt.Printf("%s\t%d\n", word, count)
}
}
実行結果は以下のようになります。
$ go run main.go
すもも 1
もも 2
うち 1
※map(連想配列)をrange
でイテレーションする際に要素を取り出す順序は保証されない(という仕様になっている)ようなので、実行するごとに上記の実行結果(単語・出現回数の表示順序)は変わります。
Ginのインストール
単語の出現回数をカウントする機能をWebアプリとして使用できるようにしていきます。
使用するWebフレームワークについては、こちらの記事]を参考にして比較的軽量かつレスポンスが高速ということでGinを選択しました。
まずは以下のコマンドを実行してGinをインストールします。
$ go get github.com/gin-gonic/gin
形態素解析の実行結果をブラウザで表示
形態素解析の実行結果をブラウザ上で表示できるようにするため、まずはテンプレートファイル(index.tpl
)を作成していきます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>go-word-counter</title>
</head>
<body>
<ul>
{{range $word, $count := .wordCount}}
<li>{{$word}}:{{$count}}回</li>
{{end}}
</ul>
</body>
</html>
上記のテンプレートファイルを読み込み、形態素解析の実行結果を埋め込んだレスポンスをブラウザ上(デフォルトではlocalhost:8080
)で表示するようにmain.go
を修正します。
なお、単語の出現回数をカウントする処理はgetWordCount()
関数を作成してそちらに移動しています。
import (
- "fmt"
+ "net/http"
"github.com/ikawaha/kagome/tokenizer"
+ "github.com/gin-gonic/gin"
)
-func main() {
+func getWordCount(text string) (map[string]int) {
dic := tokenizer.SysDicSimple()
t := tokenizer.NewWithDic(dic)
- tokens := t.Tokenize("すもももももももものうち")
+ tokens := t.Tokenize(text)
words := map[string]int{}
for _, token := range tokens {
features := token.Features()
...
}
}
- for word, count := range words {
- fmt.Printf("%s\t%d\n", word, count)
- }
+ return words
+}
+
+func main() {
+ router := gin.Default()
+ router.LoadHTMLFiles("index.tpl")
+
+ router.GET("/", func(context *gin.Context) {
+ context.HTML(http.StatusOK, "index.tpl", gin.H{
+ "wordCount": getWordCount("すもももももももものうち"),
+ })
+ })
+
+ router.Run()
}
これでmain.go
を実行してlocalhost:8080
にアクセスすると、以下のような画面が表示されます。
任意のテキストを形態素解析できるように修正
今までは固定されたテキストに対して形態素解析を実行した結果を表示していましたが、任意のテキストに対して実行できるように修正していきます。
まずは、任意のテキストを入力して送信
ボタンを押下したらテキストを/
に対してPOST送信するようにindex.tpl
を修正します。
<body>
+ <form action="/" method="post">
+ <div>
+ <textarea name="text" placeholder="テキストを入力してください"></textarea>
+ </div>
+ <div>
+ <button type="submit">送信</button>
+ </div>
+ </form>
+ <hr>
<ul>
そして、/
にPOSTでリクエストされたら送信されたテキストに対して形態素解析を実行し、その結果を画面上に表示できるようにmain.go
を修正します。
router.GET("/", func(context *gin.Context) {
+ context.HTML(http.StatusOK, "index.tpl", gin.H{})
+ })
+
+ router.POST("/", func(context *gin.Context) {
+ text := context.PostForm("text")
context.HTML(http.StatusOK, "index.tpl", gin.H{
- "wordCount": getWordCount("すもももももももものうち"),
+ "wordCount": getWordCount(text),
})
})
これで任意のテキストに対して形態素解析が実行できるようになりました!
-
送信
ボタン押下前
-
送信
ボタン押下後
ソースコード
最終的なソースコードは以下の通りです。
index.tpl
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>go-word-counter</title>
</head>
<body>
<form action="/" method="post">
<div>
<textarea name="text" placeholder="テキストを入力してください"></textarea>
</div>
<div>
<button type="submit">送信</button>
</div>
</form>
<hr>
<ul>
{{range $word, $count := .wordCount}}
<li>{{$word}}:{{$count}}回</li>
{{end}}
</ul>
</body>
</html>
main.go
package main
import (
"net/http"
"github.com/ikawaha/kagome/tokenizer"
"github.com/gin-gonic/gin"
)
func getWordCount(text string) (map[string]int) {
dic := tokenizer.SysDicSimple()
t := tokenizer.NewWithDic(dic)
tokens := t.Tokenize(text)
words := map[string]int{}
for _, token := range tokens {
features := token.Features()
// 名詞以外ならスキップ
if len(features) == 0 || features[0] != "名詞" {
continue
}
// マップに既にキーが存在する場合はインクリメント、存在しない場合は1で初期化
_, ok := words[token.Surface]
if ok {
words[token.Surface]++
} else {
words[token.Surface] = 1
}
}
return words
}
func main() {
router := gin.Default()
router.LoadHTMLFiles("index.tpl")
router.GET("/", func(context *gin.Context) {
context.HTML(http.StatusOK, "index.tpl", gin.H{})
})
router.POST("/", func(context *gin.Context) {
text := context.PostForm("text")
context.HTML(http.StatusOK, "index.tpl", gin.H{
"wordCount": getWordCount(text),
})
})
router.Run()
}
おわりに
Go言語+kagome+Ginで単語(名詞のみ)の出現回数をカウントするWebアプリを作成する手順を紹介していきました。
同様のWebアプリを実装した経験はあったものの、Go言語をはじめ今回使用したkagomeやGinといったライブラリ自体は初めて触ったので非常に良い勉強になりました!
今回はとりあえず動くコードを書くことで精一杯でしたが、今後はよりGo言語らしいコードを書けるように勉強していきたいと思います。