構文解析ではありません。主語探しゲームです。
なぜ主語を探す必要があるの?
私「あなたの年齢は?」
bot「0歳だよ」
私「あなたのお母さんの年齢は?」
bot「0歳だよ」
私「???」
↑これを無くしていこうと思います。
目次
・ソースコード
・条件文の解説
・(お前は)かわいい犬ですね ←!?
・今後の課題
・【応用】自己紹介ができるチャットボットを作る
主語や述語を探し、文の構造を明らかにする行為を「構文解析」と言います。
もちろん、既に有志による優秀な構文解析プログラムが用意されているのですが、今回はあえてそれを使わずに簡易的な関数を作って、半ばゴリ押しで主語だけを探そうと思います。
分かち書きや品詞の解析をする必要があるため、形態素解析を行います。
今回はJanomeを使っています。
※ Janomeの分かりやすい説明記事
※MeCabを使ってるよ!という方はこんなことするより同開発元の構文解析ツール「Cabocha」の使用をおすすめします。
ソースコード
from janome.tokenizer import Tokenizer #形態素解析
import re #正規表現
def Syugo(text): #主語を探す関数
syugo=""
nininsyou="名前(くん|ちゃん|さん|)|苗字(くん|ちゃん|さん|)|君|お(まえ|前)|貴様|あなた|貴方"
jyujyu=["やる","あげる","さしあげる","もらう","いただく","くれる","くださる"] #授受表現
tokens=[(token.base_form,token.part_of_speech) for token in Tokenizer().tokenize(text)] #分かち書き
tokens.append("END")
for i in range(len(tokens)):
if tokens[i]=="END":break
s0,s1=tokens[i][0],tokens[i][1] #基本形、品詞
if ("代名詞" in s1 or s0 in ["もの","物","こと","事"])and not s0=="いくつ":syugo=s0
if "名詞" in s1 and "形容詞" in tokens[i+1][1]:syugo=s0
if s0=="は" and (not "非自立" in tokens[i-1][1]) and (not tokens[i+1][0]=="?"):
if syugo=="" or syugo[-1]=="の":
syugo+=tokens[i-1][0]
break
elif s0=="の" and not tokens[i-1][0] in ["くん","ちゃん","さん"]:syugo=tokens[i-1][0]+"の"
elif s0=="を" and syugo=="":syugo="私"
elif s0=="が":syugo=tokens[i-1][0]
elif s0 in jyujyu or s0=="たい":
if not tokens[i+1][0]=="?":syugo="私"
elif syugo=="" and s0 in ["です","だ"]:syugo="私"
if s0=="?" and (tokens[i-1][0] in ["ない","ん","か","ね"]):syugo="お前"
if syugo=="" or re.search(nininsyou,syugo):syugo="お前" #主語が読み手の場合は「お前」を返す
return syugo
#ーーーここからは表示に関する記述なので、読まなくてよいーーー
textlist={
"日本の首都はどこですか?":"首都",
"今朝の目玉焼きを作ったのは誰?":"誰",
"クリスマスがやってくる":"クリスマス",
"田舎のおばあちゃんがスイカをくれた":"私",
"パフェ食べたい":"私",
"この後海行きたいと思いませんか?":"お前",
}
for text in textlist:
syugo=Syugo(text)
print("文章:",text)
ans="主語:"+syugo+" 正解:"+textlist[text]
if syugo==textlist[text]:ans="\033[34m"+ans+"\033[0m"
print(ans)
print("---------------------------------------------")
結果は以下の通りです。↓
読み手が主語の場合は「お前」を返します。
基本的に主語が抜けている(かわいい~、など)場合は「自分に言ってる」と解釈するようになっています。
そのうえで幾つかの条件を優先順に並べて適切な主語を探しています。
上記の他、幾つかの例文で試しました(が、文章パターンの取りこぼしがあると思うので、精度は完璧ではありません)。
条件文の解説
解説(ここをタップすると開きます)
if文を上から見ていきます。
▼代名詞
if ("代名詞" in s1 or s0 in ["もの","物","こと","事"])and not s0=="いくつ":syugo=s0
#例:メアリーが買ったものは人形です 主語:もの
「(年齢は)いくつ?」に対応するため今回は「いくつ」を除外していますが、場合によってはand not s0=="いくつ"を消したほうがいいかもしれません。
▼接続詞(は、が)の省略対策
if "名詞" in s1 and "形容詞" in tokens[i+1][1]:syugo=s0
#例:犬かわいい 主語:犬
▼~は
if s0=="は" and (not "非自立" in tokens[i-1][1]) and (not tokens[i+1][0]=="?"):
if syugo=="" or syugo[-1]=="の":
syugo+=tokens[i-1][0]
break
#例:犬はかわいい 主語:犬
▼○○の××は~
elif s0=="の" and not tokens[i-1][0] in ["くん","ちゃん","さん"]:syugo=tokens[i-1][0]+"の"
#例:あなたの名前は? 主語:お前
違います。
正確には「あなたの名前」です。が、今回の目的は国語の問題を解くことではなくチャットボットの自然言語処理なので、処理をわかりやすくするためにこうしています。
▼(私は)○○を××する
elif s0=="を" and syugo=="":syugo="私"
#例:犬をなでる 主語:私 ※(私は)犬を撫でる
▼~が
elif s0=="が":syugo=tokens[i-1][0]
#例:クリスマスがやってくる 主語:クリスマス
※~が、~は、等についての解説
田舎のおばあちゃんがくれたスイカは実が甘くておいしい
→「~が」で主語が「おばあちゃん」になる
→「~は」で主語が「スイカ」に塗り替えられ、処理中断(break)
▼授受表現と疑問形
elif s0 in jyujyu or s0=="たい":
if not tokens[i+1][0]=="?":syugo="私"
#パフェをおごってくれた 主語:私 ※くれる=授受表現(話し手がしてもらう)
#パフェ食べたい 主語:私(話し手)
#パフェ食べたい? 主語:お前(聞き手)
#jyujyu=["やる","あげる","さしあげる","もらう","いただく","くれる","くださる"]
if s0=="?" and (tokens[i-1][0] in ["ない","ん","か","ね"]):syugo="お前"
#海行かない? 主語:お前
▼主語が無い&言い切り
elif syugo=="" and s0 in ["です","だ"]:syugo="私"
#幸せだ 主語:私(多くの場合は)
▼主語がなかった/二人称が入っていた場合
if syugo=="" or re.search(nininsyou,syugo):syugo="お前"
#例:あなたの名前は? 主語:お前(表記ゆれ対策)
日本語特有の微妙なニュアンスによる主語の違いにも対応できます。
…ん?
(お前は)かわいい犬ですね ←!?
一般的には「(この犬は)かわいい犬ですね」です。
しかし「この犬は」という主語が抜けているため「(お前は)かわいい犬ですね」と読み「SMプレイをしてるんだな」と解釈することもできます。できるんです。(そしてこの関数は主語が無い場合自分が言われてると解釈します)困りましたね。
なぜこのようなことが起こるかというと、人間同士では見てる対象や状況、前後の文脈、添付された画像などによって主語を推測することができるからです。また常識があるため、大半の日本語話者は自分が犬だと思っていません。
対策として
・主語が無い場合、文中に出てきた名詞を主語として扱う
・前の文章/文脈から主語を補完する
・「お前」が人間であることをあらかじめ定義する
などが挙げられますが、今回は簡易版なのでそこまで厳密にはやりません。
今後の課題
・表記ゆれに対応できていない(?と?、カナ、漢字など)
・前の文章から主語を補完する機能が必要
「あなたの名前は?」「年齢は?」→主語=あなた
・形態素解析の仕様上、おばあちゃんが「ばあちゃん」になる
・もうちょっとスマートな書き方が出来たと思う
【応用】自己紹介ができるbotを作る
prof={ #プロフィール
"苗字":"佐藤",
"名前":"bot",
"年齢|いくつ|歳|才":"0歳",
}
#returnが「お前」の場合のみ、キーワードマッチでprofの情報を答える。
while True:
text="わからない"
usertext=input("Q.")
for state in prof:
if re.search(state,usertext) and Syugo(usertext)=="お前":text=prof[state]+"だよ"
print("A."+text)
結果は上のようになります。
returnに二人称が含まれる場合のみ、キーワードマッチでプロフィール内の情報を答えることで、冒頭のような会話のミスを無くすことができました。
以上です。ありがとうございました。


