ブログ名

【第9回】5W1H抽出AI 係り受け解析による精度向上の検討

現在の課題

現在の課題は、連用形の場合を考慮できないこと(「〜する人」などの場合「〜する」をHowとしてしまう)です。
つまり、修飾語を区別できていません。
この問題に対し、係り受け解析を行うことで解決できないかどうかを検討しました。

係り受け解析

係り受け解析器というのは文章を形態素に分けた後に、単語間の修飾関係を解析することです。 たとえば、以下のような文章があるとします。 僕のランチ代はおおよそ200円でした。 僕の → ランチ代は ランチ代は → 200円でした。 おおよそ → 200円でした。 と修飾されます。

https://sitest.jp/blog/?p=11912 より引用

係り受け解析を行えるフリーソフトとしてCaboCha(南瓜)があります。 こちらを参考にインストールして実際に係り受け解析を行うと、下記の通りになります。

f:id:iTD_GRP:20200710152055p:plain
係り受け解析結果

検討方針

抽出前に形態素解析係り受け解析を行い、係り受け解析による修飾関係をもとに分類が難しい要素(HowやWhat, Why)を正しく分類する方法を検討します。

CaboChaAnalyzerのセットアップ

python係り受け解析器CaboChaを扱いやすくするラッパーライブラリがあったので導入します。
https://github.com/kenkov/cabocha/blob/master/README.rst

このライブラリでは、下記のようにCaboChaAnalyzerに文を入力するとトークンと呼ばれる言葉の最小単位に分けることができます。

ruby:python
from cabocha.analyzer import CaboChaAnalyzer
analyzer = CaboChaAnalyzer()
tree = analyzer.parse("日本語の形態素解析はすごい")

実行結果: Token("日本語") Token("の") Token("形態素") Token("解析") Token("は") Token("すごい")

トークンよりも一つ大きい単位となるのがチャンクです(例,"日本語の")。 下記のようにすることで、あるチャンクの係り受け先かどうかがわかります。

ruby:python
chunks = tree.chunks
start_chunk = chunks[0]
start_chunk.next_link

実行結果: Chunk("形態素解析は")

この場合、「日本語の」の係り受け先が「形態素解析は」であることを表しています。
また、下記のようにすることでチャンクの係り元がわかります。

ruby:python
end_chunk = chunks[-1]
end_chunk.prev_links

実行結果: [Chunk("形態素解析は")]

ここで、実際の音声認識による文章を入力した時に問題が生じました。

入力: "混雑が始まりましたjr各社によりますと"

このように、文で区切られていない入力をすると「始まりました」の係り受け先はないはずですが、
「jr各社に」が係り受け先として抽出されてしまいました。
これは結果的にHow要素の抽出の精度を下げることになってしまうので、この方法では連体形を正しく見分けることができない可能性が高いです。
ただし、一文一文を区切ることができれば有効だと思われます。

精度向上

前回 → 今回 ((回避How)するためにWhat) → (回避するためにWhat) 痩せ子(さんのことをWhat) → (痩せ子さんのことをWhat) つきたくないんで → (つきたくないWhy)んで

ソースコード

ruby:python
#特徴的パターン
#特徴的パターン
d = ['病院', '大学'] #Whereのパターン

dare = ['が','は'] #Whoの後に続く助詞
nani = ['を','に'] #Whatの後に続く助詞
naze =['だから', 'それで', 'そのため', 'このため', 'そこで', 'したがって', 'ゆえに', 'それゆえに','から','んで']#Whyの後に続く接続助詞
suji = ['0','1','2','3','4','5','6','7','8','9']
itu1 = ['今日','昨日','明日','午前','午後','明後日','本日','年末'] #Whenのパターン1
itu2 = ['年','月','日'] #Whenのパターン2
words2 = []
poss2 = []
itu3 = ''.join(itu1)
num = len(words)-1
while num > -1:

##Where#
    if poss[num-1][2] == '地域' : #一つ先を探索
            words.insert(num,'Where)')
            words.insert(num-1,', (') 
####

##When##
    for c in itu1:
        if c in words[num-1]: #一つ先を探索
            words.insert(num,'When)')
            words.insert(num-1,', (')
            num -= 1

    for c in itu2:
        if (c in words[num-1]) and poss[num-1][1]=='固有名詞':
                for s in suji:
                    if s in words[num-1] :
                        words.insert(num,'When)')
                        words.insert(num-1,', (')
                        num -= 1


####

##Why##
    for c in naze:   
        if words[num] == c: #続く助詞がWhyの後に続くものの場合
            words.insert(num,'Why)')
            while num > 0:
                if (poss[num][0] =='名詞' and poss[num][1] != '非自立' and poss[num][1] != '接尾') or (poss[num][1] == '自立'  and poss[num][6] != 'する' and poss[num][6] != 'なる'):
                    break
                num -= 1
                    

            words.insert(num,', (')

####    
    
##Who##    
    for c in dare:    
        if poss[num][0] == '助詞' and words[num] == c: #続く助詞がWhoの後に続くものの場合
                words.insert(num,'Who)')
                while num > 0:
                    if (poss[num][0] =='名詞' and poss[num][1] != '非自立' and poss[num][1] != '接尾') or (poss[num][1] == '自立'  and poss[num][6] != 'する' and poss[num][6] != 'なる'):
                        break
                    num -= 1
                    

                words.insert(num,', (')

####


        
##How##    
    if poss[num][1] == '自立' and poss[num][5] == '基本形':
   
        words.insert(num+1,'How)')
        if poss[num][6] == 'する' or poss[num][6] == 'なる':
            num -= 1
                
        words.insert(num,', (') 
        
    elif poss[num][1] == '接尾' and poss[num][5] == '基本形':
        
        words.insert(num+1,'How)')
        num -= 2
        words.insert(num,', (')      
            #print('{0} , How'.format(sw))
        
    elif poss[num][0] == '助動詞' and poss[num][5] == '基本形':
        sw =[]
        words.insert(num+1,'How)')
        num -= 1
        while num > 0:
            
            if (poss[num][0] =='名詞' and poss[num][1] != '非自立') or (poss[num][1] == '自立' and poss[num][6] != 'する' and poss[num][6] != 'なる'):
                break
            num -= 1
        
        if  words[num] == 'ん':
            while num>0:
                num -=1
                if (poss[num][0] =='名詞' and poss[num][1] != '非自立') or (poss[num][1] == '自立' and poss[num][6] != 'する' and poss[num][6] != 'なる'):
                    break
                    
        words.insert(num,', (')

####

##What## 
    elif poss[num][2] == '引用': #続く助詞がWhatの後に続くものの場合2
        words.insert(num+1,'What)')
       
        while num>0:
            
            num -=1
            if (poss[num][0] =='名詞' and poss[num][1] != '非自立' and poss[num][1] != '接尾') or (poss[num][1] == '自立' and poss[num][6] != 'する' and poss[num][6] != 'なる'):
                break
        
        words.insert(num,'(')
        
    for c in nani:
        if poss[num][0] == '助詞' and words[num] == c: #続く助詞がWhatの後に続くものの場合1
            words.insert(num+1,'What)')
            while num>0:
            
                num -= 1
                if (poss[num][0] =='名詞' and poss[num][1] != '非自立' and poss[num][1] != '接尾') or (poss[num][1] == '自立' and poss[num][6] != 'する' and poss[num][6] != 'なる'):
                    break
        
            words.insert(num,'(')    
#            num += 1       
    else:
        num -= 1

次回の予定

係り受け解析は少し保留にし、助詞などを手がかりとして精度の向上を検討していきます。


次の記事へ

前の記事へ 戻る