iMind Developers Blog

iMind開発者ブログ

Ginzaで形態素解析、係り受け解析、固有表現抽出、ユーザー辞書追加

概要

Ginzaを使ってNLPでよく使ういくつかの処理を動かしてみる。

バージョン情報

  • ginza==2.2.0
  • Python 3.7.4

インストール

pipで入れられる。

$ pip install "https://github.com/megagonlabs/ginza/releases/download/latest/ginza-latest.tar.gz"

詳細は下記参照。

https://megagonlabs.github.io/ginza/

形態素解析

Ginzaは内部的にはSudachiPyを利用している。

import spacy
nlp = spacy.load('ja_ginza')
doc = nlp('庭にいる犬が鳴いてる')
for sent in doc.sents:
    for token in sent:
        print(
            'token.i={}'.format(token.i),
            'token.orth_={}'.format(token.orth_),
            'token.lemma_={}'.format(token.lemma_), 
            'token.pos_={}'.format(token.pos_),
            'token.tag_={}'.format(token.tag_),
            'token.dep_={}'.format(token.dep_),
            'token.head.i={}'.format(token.head.i),
            'token.children={}'.format(list(token.children)),
            'token.lefts={}'.format(list(token.lefts)),
            'token.rights={}'.format(list(token.rights)),
            'token.head.text={}'.format(token.head.text)
        )

    #=>token.i=0 token.orth_=庭 token.lemma_=庭 token.pos_=NOUN token.tag_=名詞-普通名詞-一般 token.dep_=iobj token.head.i=2 token.children=[に] token.lefts=[] token.rights=[に] token.head.text=いる
    #=> token.i=1 token.orth_=に token.lemma_=に token.pos_=ADP token.tag_=助詞-格助詞 token.dep_=case token.head.i=0 token.children=[] token.lefts=[] token.rights=[] token.head.text=庭
    #=> token.i=2 token.orth_=いる token.lemma_=居る token.pos_=VERB token.tag_=動詞-非自立可能 token.dep_=acl token.head.i=3 token.children=[庭] token.lefts=[庭] token.rights=[] token.head.text=犬
    #=> token.i=3 token.orth_=犬 token.lemma_=犬 token.pos_=NOUN token.tag_=名詞-普通名詞-一般 token.dep_=nsubj token.head.i=5 token.children=[いる, が] token.lefts=[いる] token.rights=[が] token.head.text=鳴い
    #=> token.i=4 token.orth_=が token.lemma_=が token.pos_=ADP token.tag_=助詞-格助詞 token.dep_=case token.head.i=3 token.children=[] token.lefts=[] token.rights=[] token.head.text=犬
    #=> token.i=5 token.orth_=鳴い token.lemma_=鳴く token.pos_=VERB token.tag_=動詞-一般 token.dep_=ROOT token.head.i=5 token.children=[犬, てる] token.lefts=[犬] token.rights=[てる] token.head.text=鳴い
    #=> token.i=6 token.orth_=てる token.lemma_=てる token.pos_=AUX token.tag_=助動詞 token.dep_=aux token.head.i=5 token.children=[] token.lefts=[] token.rights=[] token.head.text=鳴い

品詞(tag)、原型(lemma)、関係(children, lefts, rights)など様々な情報が取れる。

dep_ の意味はこちら

https://github.com/clir/clearnlp-guidelines/blob/master/md/specifications/dependency_labels.md

pos_の意味はこちら

https://spacy.io/api/annotation#pos-tagging

係り受け解析

noun_chunkで名詞の係り受け関係がシンプルに出せる。

import spacy

nlp = spacy.load('ja_ginza')
doc = nlp("庭にいる犬が鳴いてる")

for chunk in doc.noun_chunks:
    print(chunk.lemma_, chunk.root.dep_, chunk.root.head.lemma_)

    #=> 庭 iobj 居る
    #=> 犬 nsubj 鳴く

全体的な係り受け関係を確認。

import spacy
nlp = spacy.load('ja_ginza')
doc = nlp('庭にいる犬が鳴いてる')
for sent in doc.sents:
    for token in sent:
        print(token.lemma_, token.dep_, token.head.lemma_, token.tag_, token.head.tag_)

    #=> 庭 iobj 居る 名詞-普通名詞-一般 動詞-非自立可能
    #=> に case 庭 助詞-格助詞 名詞-普通名詞-一般
    #=> 居る acl 犬 動詞-非自立可能 名詞-普通名詞-一般
    #=> 犬 nsubj 鳴く 名詞-普通名詞-一般 動詞-一般
    #=> が case 犬 助詞-格助詞 名詞-普通名詞-一般
    #=> 鳴く ROOT 鳴く 動詞-一般 動詞-一般
    #=> てる aux 鳴く 助動詞 動詞-一般

出力は原形、dep_、係る言葉の原形、品詞、係る言葉の品詞の5項目。

dep_の意味が難しい。caseは「case maker」で格助詞、aclは「Clausal modifier of noun」で名詞節(庭に居る犬)への修飾で連体修飾。nsubjは「Nominal subject」だから主語? auxは「Auxiliary」で助動詞。

その他、適当に3文ほど上記処理にかけてみる。

この蜜柑の産地を教えて

此の det 蜜柑 連体詞 名詞-普通名詞-一般
蜜柑 nmod 産地 名詞-普通名詞-一般 名詞-普通名詞-一般
の case 蜜柑 助詞-格助詞 名詞-普通名詞-一般
産地 obj 教える 名詞-普通名詞-一般 動詞-一般
を case 産地 助詞-格助詞 名詞-普通名詞-一般
教える ROOT 教える 動詞-一般 動詞-一般
て mark 教える 助詞-接続助詞 動詞-一般

detは「Determiner」でtheとかthisのような限定詞。nmodとmarkの日本語訳がわからない。。。

程々に頑張って改善する

程々 iobj 頑張る 名詞-普通名詞-形状詞可能 動詞-一般
だ case 程々 助動詞 名詞-普通名詞-形状詞可能
頑張る advcl 改善 動詞-一般 名詞-普通名詞-サ変可能
て mark 頑張る 助詞-接続助詞 動詞-一般
改善 ROOT 改善 名詞-普通名詞-サ変可能 名詞-普通名詞-サ変可能
為る aux 改善 動詞-非自立可能 名詞-普通名詞-サ変可能
トランプ大統領が来日した頃の株価を見る

トランプ compound 大統領 名詞-普通名詞-一般 名詞-普通名詞-一般
大統領 nsubj 来日 名詞-普通名詞-一般 名詞-普通名詞-サ変可能
が case 大統領 助詞-格助詞 名詞-普通名詞-一般
来日 acl 頃 名詞-普通名詞-サ変可能 名詞-普通名詞-副詞可能
為る aux 来日 動詞-非自立可能 名詞-普通名詞-サ変可能
た aux 来日 助動詞 名詞-普通名詞-サ変可能
頃 nmod 株価 名詞-普通名詞-副詞可能 名詞-普通名詞-一般
の case 頃 助詞-格助詞 名詞-普通名詞-副詞可能
株価 obj 見る 名詞-普通名詞-一般 動詞-非自立可能
を case 株価 助詞-格助詞 名詞-普通名詞-一般
見る ROOT 見る 動詞-非自立可能 動詞-非自立可能

compoundは複合語。

固有表現抽出

日付や地名等の固有表現を抽出する。

import spacy

nlp = spacy.load('ja_ginza')
doc = nlp("今日は2019/10/08、火曜。豊島区の家からスカイツリーが見えます。")

for ent in doc.ents: 
    print(ent.text, ent.start_char, ent.end_char, ent.label_) 

    #=> 今日 0 2 DATE
    #=> 2019/10/08 3 13 DATE
    #=> 豊島区 17 20 LOC
    #=> スカイツリー 24 30 ORG

DATE, LOC, ORGの3つが検出されている。

他にもMONEY, PERSON,

安い時期は10,000円で台湾に行けます

10,000円 5 12 MONEY
台湾 13 15 LOC
トランプ大統領が26日、大相撲を観戦した

トランプ 0 4 PERSON
26日 8 11 DATE
明日の10:00以降の降水確率は20%

明日 0 2 DATE
10:00 3 8 TIME
20% 14 17 PERCENT

10,000円を\10,000にすると取れなかったり、10:00を10時にすると取れなかったり、対応していない固有表現も多い。

ユーザー辞書追加

例として小林製薬を追加してみる。

デフォルトでは小林と製薬の2つに分かれる。

token.orth_=小林 token.tag_=名詞-固有名詞-人名-姓
token.orth_=製薬 token.tag_=名詞-普通名詞-一般

これが1語になるようにユーザー辞書に追加する。

ユーザー辞書はCSVで記述する。詳細は下記参照。

https://github.com/WorksApplications/Sudachi/blob/develop/docs/user_dict.md

今回追加するのは固有名詞。なので上記サイトに載っている例で似ている行をコピーして改変する。

コンピュータ学院,4786,5146,8000,コンピュータ学院,名詞,固有名詞,一般,*,*,*,コンピュータガクイン,コンピューター学院,*,*,*,*,*

↓ 改変後

小林製薬,4786,5146,3000,小林製薬,名詞,固有名詞,一般,*,*,*,コバヤシセイヤク,小林製薬,*,*,*,*,*

スコアを8000 → 3000にしているのは弱すぎると分割されそうなのでなんとなく。

これを仮にuserdic.csvという名前にして保存しておく。

sudachipyのコマンドで辞書を生成する。

sudachipy ubuild  -s {システム辞書のパス} {今回作ったcsvのパス}

システム辞書はcondaで入れたら下記のパスに入っている。(minicondaが$HOME直下に入っていてpython3.7の場合。見つからない場合はどこかに入っているはずなのでファイル検索する)

$HOME/miniconda3/lib/python3.7/site-packages/sudachidict/resources/system.dic

sudachipy ubuild  \
    -s $HOME/miniconda3/lib/python3.7/site-packages/sudachidict/resources/system.dic \
    userdic.csv

実行すると user.dic というファイルが生成される。

あとはsudachi.jsonにユーザー辞書を書き加えてそれを読み込んだTokenizerをnlpに設定してあげると動く。

簡易な方法として、conda配下にいるginzaから読み込んでいるjsonを書き換えると、Tokenizer等を自作しないでも反映できる。

minicondaが$HOME配下にインストールされている場合、下記のパスにsudachi.jsonがいる。

$HOME/miniconda3/lib/python3.7/site-packages/sudachipy/resources/sudachi.json

ここにuserDictという要素を追加する。下記の3行目の記述。

{
    "characterDefinitionFile" : "char.def",
    "userDict" : [ "先ほど生成したuser.dicのパス" ],
    "inputTextPlugin" : [

これで強引だけどユーザー辞書が設定された。

下記のように登録したワードが1語として認識される。

import spacy
nlp = spacy.load('ja_ginza')
doc = nlp('小林製薬')
for sent in doc.sents:
    for token in sent:
        print(
            'token.orth_={}'.format(token.orth_),
            'token.tag_={}'.format(token.tag_),
        )

    #=> token.orth_=小林製薬 token.tag_=名詞-固有名詞-一般

自作のTokenizerを設定する

続いてSudachiTokenizerをいじったMySudachiTokenizerクラスを作って動かす方法。

condaの中にいる必要なファイルを持ってくる。

cp -r ~/miniconda3/lib/python3.7/site-packages/sudachipy/resources/ .

上記が見つからない場合はSudachiPyから持ってくる。

https://github.com/WorksApplications/SudachiPy/tree/develop/sudachipy/resources

持ってきたresourcesの中にuser.dicも入れる。下記のようなファイル構成になる。

resources/
├── char.def
├── rewrite.def
├── sudachi.json
├── unk.def
└── user.dic

resource/sudachi.jsonのユーザー辞書を記述(今回はファイル名だけで大丈夫)。

    "userDict" : [ "user.dic" ],
# 独自のDictionaryの指定を入れたクラス
import sudachipy
from ginza import sudachi_tokenizer
class MySudachiTokenizer(sudachi_tokenizer.SudachiTokenizer):
    def __init__(self, nlp):
        self.nlp = nlp
        self.vocab = nlp.vocab
        split_mode = sudachipy.tokenizer.Tokenizer.SplitMode.C
        dict_ = sudachipy.dictionary.Dictionary(
            config_path='resources/sudachi.json',
            resource_dir='resources')
        self.tokenizer = dict_.create(mode=split_mode)
        self.use_sentence_separator = True

あとはnlpのTokenizerを上記のMyの方に変更すれば辞書や設定を独自に指定したTokenizerが使える。

import spacy
nlp = spacy.load('ja_ginza')
nlp.tokenizer = MySudachiTokenizer(nlp)
doc = nlp('小林製薬')
for sent in doc.sents:
    for token in sent:
        print(
            'token.orth_={}'.format(token.orth_),
            'token.tag_={}'.format(token.tag_),
        )
    #=> token.orth_=小林製薬 token.tag_=名詞-固有名詞-一般

改定履歴

Author: Masato Watanabe, Date: 2019-10-14, 記事投稿