ブログ名

【第12回】5W1H抽出AI KNPを用いた精度向上

KNPのセットアップ

前回の記事の通り、KNPを使用するとより精度が向上すると考えられるため、以下の記事を参考にKNPをセットアップしました。
qiita.com

以下のマニュアルを参考に実際に動作させて、cabochaとの違いを確認してみます。
nlp.ist.i.kyoto-u.ac.jp

まずは、形態素解析ソフトJUMAN++から実行してみました。

  • 入力
下鴨神社の参道は暗かった。  
  • 出力
見出し:下鴨, 読み:しもがも, 原形:下鴨, 品詞:名詞, 品詞細分類:地名, 活用型:*, 活用形:*, 意味情報:自動獲得:Wikipedia Wikipedia地名, 代表表記:<br>
見出し:神社, 読み:じんじゃ, 原形:神社, 品詞:名詞, 品詞細分類:普通名詞, 活用型:*, 活用形:*, 意味情報:代表表記:神社/じんじゃ 地名末尾 カテゴリ:場所-施設 ドメイン:文化・芸術, 代表表記:神社/じんじゃ<br>
見出し:の, 読み:の, 原形:の, 品詞:助詞, 品詞細分類:接続助詞, 活用型:*, 活用形:*, 意味情報:NIL, 代表表記:<br>
見出し:参道, 読み:さんどう, 原形:参道, 品詞:名詞, 品詞細分類:普通名詞, 活用型:*, 活用形:*, 意味情報:代表表記:参道/さんどう カテゴリ:場所-施設 ドメイン:文化・芸術, 代表表記:参道/さんどう<br>
見出し:は, 読み:は, 原形:は, 品詞:助詞, 品詞細分類:副助詞, 活用型:*, 活用形:*, 意味情報:NIL, 代表表記:<br>
見出し:暗かった, 読み:くらかった, 原形:暗い, 品詞:形容詞, 品詞細分類:*, 活用型:イ形容詞アウオ段, 活用形:タ形, 意味情報:代表表記:暗い/くらい, 代表表記:暗い/くらい<br>
見出し:。, 読み:。, 原形:。, 品詞:特殊, 品詞細分類:句点, 活用型:*, 活用形:*, 意味情報:NIL, 代表表記:<br>

このように、MeCabでは解析できなかった意味情報やカテゴリ、ドメイン等が抽出可能ですが、問題があります。
MeCabでは固有名詞を認識して以下のように形態素解析するのですが、

  • 出力
下鴨神社,['名詞', '固有名詞', '組織', '*', '*', '*', '下鴨神社', 'シモガモジンジャ', 'シモガモジンジャ']<br>
の,['助詞', '連体化', '*', '*', '*', '*', 'の', 'ノ', 'ノ']<br>
参道,['名詞', '一般', '*', '*', '*', '*', '参道', 'サンドウ', 'サンドー']<br>
は,['助詞', '係助詞', '*', '*', '*', '*', 'は', 'ハ', 'ワ']<br>
暗かっ,['形容詞', '自立', '*', '*', '形容詞・アウオ段', '連用タ接続', '暗い', 'クラカッ', 'クラカッ']<br>
た,['助動詞', '*', '*', '*', '特殊・タ', '基本形', 'た', 'タ', 'タ']<br>
。,['記号', '句点', '*', '*', '*', '*', '。', '。', '。']<br>

このように、「下鴨神社」等の固有名詞の場合はJUMAN++とMecab形態素の数が異なってしまいます。
そのためCaboChaは使用せず、形態素解析係り受け解析はKNPで行うことにしました。
KNPの出力の出力はCaboChaよりも複雑で, 以下のようになります。

  • 出力
文節<br>
    ID:0, 見出し:下鴨神社の, 係り受けタイプ:D, 親文節ID:1, 素性:<文頭><地名><助詞><連体修飾><体言><係:ノ格><区切:0-4><準主題表現><正規化代表表記:下鴨/しもがも+神社/じんじゃ><主辞代表表記:神社/じんじゃ><br>
    ID:1, 見出し:参道は, 係り受けタイプ:D, 親文節ID:2, 素性:<ハ><助詞><体言><係:未格><提題><区切:3-5><主題表現><格要素><連用要素><正規化代表表記:参道/さんどう><主辞代表表記:参道/さんどう><br>
    ID:2, 見出し:暗かった。, 係り受けタイプ:D, 親文節ID:-1, 素性:<文末><時制-過去><句点><用言:形><レベル:C><区切:5-5><ID:(文末)><係:文末><提題受:30><主節><格要素><連用要素><状態述語><正規化代表表記:暗い/くらい><主辞代表表記:暗い/くらい><br>
基本句<br>
    ID:0, 見出し:下鴨, 係り受けタイプ:D, 親基本句ID:1, 素性:<文節内><係:文節内><文頭><地名><体言><名詞項候補><先行詞候補><SM-場所><正規化代表表記:下鴨/しもがも><NE内:LOCATION><br>
    ID:1, 見出し:神社の, 係り受けタイプ:D, 親基本句ID:2, 素性:<地名><助詞><連体修飾><体言><係:ノ格><区切:0-4><準主題表現><名詞項候補><先行詞候補><SM-場所><係チ:非用言格解析||用言&&文節内:T解析格-ヲ><正規化代表表記:神社/じんじゃ><NE:LOCATION:下鴨神社><br>
    ID:2, 見出し:参道は, 係り受けタイプ:D, 親基本句ID:3, 素性:<ハ><助詞><体言><係:未格><提題><区切:3-5><主題表現><格要素><連用要素><名詞項候補><先行詞候補><正規化代表表記:参道/さんどう><解析格:ガ><br>
    ID:3, 見出し:暗かった。, 係り受けタイプ:D, 親基本句ID:-1, 素性:<文末><時制-過去><句点><用言:形><レベル:C><区切:5-5><ID:(文末)><係:文末><提題受:30><主節><格要素><連用要素><状態述語><正規化代表表記:暗い/くらい><用言代表表記:暗い/くらい><主題格:一人称優位><格関係2:ガ:参道><格解析結果:暗い/くらい:形22:ガ/N/参道/2/0/1;ニ/U/-/-/-/-;ト/U/-/-/-/-;カラ/U/-/-/-/-;時間/U/-/-/-/-;修飾/U/-/-/-/-;ガ2/U/-/-/-/-;ニクラベル/U/-/-/-/-;外の関係/U/-/-/-/-><br>
形態素<br>
    ID:0, 見出し:下鴨, 読み:しもがも, 原形:下鴨, 品詞:名詞, 品詞細分類:地名, 活用型:*, 活用形:*, 意味情報:自動獲得:Wikipedia Wikipedia地名 疑似代表表記 代表表記:下鴨/しもがも, 代表表記:下鴨/しもがも<br>
    ID:1, 見出し:神社, 読み:じんじゃ, 原形:神社, 品詞:名詞, 品詞細分類:普通名詞, 活用型:*, 活用形:*, 意味情報:代表表記:神社/じんじゃ 地名末尾 カテゴリ:場所-施設 ドメイン:文化・芸術, 代表表記:神社/じんじゃ<br>
    ID:2, 見出し:の, 読み:の, 原形:の, 品詞:助詞, 品詞細分類:接続助詞, 活用型:*, 活用形:*, 意味情報:NIL, 代表表記:<br>
    ID:4, 見出し:は, 読み:は, 原形:は, 品詞:助詞, 品詞細分類:副助詞, 活用型:*, 活用形:*, 意味情報:NIL, 代表表記:<br>
    ID:5, 見出し:暗かった, 読み:くらかった, 原形:暗い, 品詞:形容詞, 品詞細分類:*, 活用型:イ形容詞アウオ段, 活用形:タ形, 意味情報:代表表記:暗い/くらい, 代表表記:暗い/くらい<br>
    ID:6, 見出し:。, 読み:。, 原形:。, 品詞:特殊, 品詞細分類:句点, 活用型:*, 活用形:*, 意味情報:NIL, 代表表記:<br>

参考記事によると、係り受けタイプとはそれを下記を表しているようです。

  • D:通常の係り受け関係
  • P:並列の場合
  • A:同格
  • I:部分並列

文節毎や基本句、形態素毎に「素性」と呼ばれる詳細な解析結果を参照できますが、5W1Hの抽出には文節ごとの素性を確認すれば良さそうです。
ただし、MeCabと異なり素性の数が決まっていないため、注意が必要です。

参考記事
pyKNPについてのメモ(自分用) · GitHub

5w1h抽出への応用

まだ調整が必要ですが、Who/When/Where/What/HowをKNPを使って抽出してみました。
ソースコードを以下に示します。
大分シンプルになりました。

python

from pyknp import KNP
import re
When = []
Where = []
What = []
Why = []
Who = []
How = []
nani = ['ガ','ヲ','ノ'] #Whatの後に続く助詞

knp = KNP(option='-tab')     # Default is JUMAN++. If you use JUMAN, use KNP(jumanpp=False)
result = knp.parse("田中さん病院行く")


for mrph in result.mrph_list(): # 各形態素へのアクセス
    so_f = re.split("[-:]", mrph.imis) #feature分割
    if mrph.bunrui == '地名' or '場所' in so_f:
        Where.append(mrph.midasi)
    elif '時間' in so_f:
        When.append(mrph.midasi)
    elif '人' in so_f or '人名' in so_f:
        Who.append(mrph.midasi)
        
for bnst in result.bnst_list(): # 各文節へのアクセス1
    setu_f = re.split("[><]", bnst.fstring)
    del setu_f[::2]
    if '主節' in setu_f:
        How.append("".join(mrph.midasi for mrph in bnst.mrph_list()))
        how_id = bnst.bnst_id

        
for bnst in result.bnst_list(): # 各文節へのアクセス2
    setu_f = re.split("[><]", bnst.fstring)
    del setu_f[::2]
    for c in nani:
        if bnst.parent_id == how_id and  '格要素' in setu_f and c in setu_f:
            What.append("".join(mrph.midasi for mrph in bnst.mrph_list()))
            break

        
print('Who:%s' %(Who))
print('Where:%s' %(Where))
print('When:%s' %(When))
print('What:%s' %(What))
print('How:%s' %(How))

実行例

入力1:田中さん病院行く。
出力1:
Who:['田中']
Where:['病院']
When:
What:

How:['行く']

入力2:"年末年始をふるさとなどで過ごす人たちで各交通機関の混雑が始まりました"
出力2:
Who:['人']
Where:['ふるさと']
When:['年', '末', '年始']
What:['混雑が']
How:['始まりました']

入力3: 各社によりますと東海道新幹線の下りは31日の午前中まで指定席はほぼ満席です
出力3:
Who:
Where:['東海道', '席']
When:['日', '午前']
What:

How:['満席です']

出力1の結果からは、「病院」をWhere要素として抽出できていることがわかります。
出力2の結果からは、「ふるさと」をWhere要素として抽出できたことがわかります。Where要素は抽出できているようです。
しかし、出力2ではWho要素、出力3ではWhen要素をうまく抽出できていませんでした。

次回の予定

次回も、引き続きKNPを用いた手法を検討していきます。


次の記事へ

前の記事へ 戻る