92
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

posted at

文書からFAQを自動生成する試み

本文書について

タイトルは少し盛りました。
frequentryではありません。すみません

本文書では、COTOHA API構文解析機能を使って、任意のテキストから質問と回答のペアを生成する「QA生成器」について述べる。

QA生成器

背景

商品文書やらチャットデータから、FAQを作りたいというニーズは結構あるらしい。
ものは試しと作ったQA生成器だが、割と面白いと好評だったので調子にのって公開してみる。

概要

↓下記のように解析される文があったとして

今朝おばあさんが川から拾った桃を切ろうとしたら、桃が突然割れて中から男の子が飛び出してきた

↓こうすれば質問文が作れる。
Picture2.png

↓さらに、場所や時間に関する質問文も作れる。
Picture3.png
Picture4.png

このように、構文解析結果に objectplacetime などの、深層格情報が含まれているため、ある程度のバリエーションを持った質問文を作ることができる。

動作環境

python3.6.2

スクリプト全体

クリックして開いてください
# -*- coding: utf-8 -*-

import os
import json
import requests
import pickle

CLIENT_ID = 'XXX'
CLIENT_SECRET = 'XXX'
API_BASE_URL = 'https://api.ce-cotoha.com/api/dev/nlp/'
ACCESS_TOKEN_PUBLISH_URL = 'https://api.ce-cotoha.com/v1/oauth/accesstokens'

def get_access_token():
    headers = {'Content-Type': 'application/json',
               'charset': 'UTF-8',}
    data = {'grantType':'client_credentials',
            'clientId':CLIENT_ID,
            'clientSecret':CLIENT_SECRET}
    data = json.dumps(data)
    response = requests.post(ACCESS_TOKEN_PUBLISH_URL, headers=headers, data=data)
    response = json.loads(response.text)
    return response['access_token']

if not os.path.isfile('./ACCESS_TOKEN.pickle'):
    ACCESS_TOKEN = get_access_token()
    with open('ACCESS_TOKEN.pickle', mode='wb') as f:
        pickle.dump(ACCESS_TOKEN, f)
with open('ACCESS_TOKEN.pickle', mode='rb') as f:
    ACCESS_TOKEN = pickle.load(f)

def parse(sentence):
    global ACCESS_TOKEN
    headers = {'Content-Type': 'application/json',
               'charset': 'UTF-8',
               'Authorization': 'Bearer '+ACCESS_TOKEN}
    data = {'sentence':sentence,}
    data= json.dumps(data)
    response = requests.post(API_BASE_URL+'v1/parse', headers=headers, data=data)
    response = json.loads(response.text)
    if response['status'] == 99998:
        ACCESS_TOKEN = get_access_token()
        with open('ACCESS_TOKEN.pickle', mode='wb') as f:
            pickle.dump(ACCESS_TOKEN, f)
        return parse(sentence)
    return response

class QAGeneration:
    def __init__(self):
        self.parsed_sentence = None
        self.dependencies = list()
        self.chunkid2text = dict()

    def _extract_dependencies(self,jsonfile):
        chunkid2text = dict()
        dependencies = list()
        # 解析結果(json)から係り受け情報を抽出
        for chunk in jsonfile["result"]:
            chunk_id = chunk["chunk_info"]["id"]
            tokens = [token["form"] for token in chunk["tokens"]]
            chunkid2text[chunk_id] = "".join(tokens)
            for link in chunk["chunk_info"]["links"]:
                dependencies.append([chunk_id,link["link"],link["label"]])
        return dependencies,chunkid2text
    def _TorF_id_in_subtree_root_id(self,id,subtree_root_id):
        checklist = [item for item in self.dependencies if item[0]==subtree_root_id]
        if id in [item[1] for item in checklist]:
            return True
        else:
            for p,c,_ in checklist:
                if c in [item[0] for item in self.dependencies]:
                    return self._TorF_id_in_subtree_root_id(id,c)
            return False
    def _get_subtree_texts(self,subtree_root_id):
        parent_ids = [item[0] for item in self.dependencies]
        if subtree_root_id not in parent_ids:
            return self.chunkid2text[subtree_root_id]
        else:
            text = ''
            for item in self.dependencies:
                if item[0]!=subtree_root_id:continue
                text += self._get_subtree_texts(item[1])
            text += self.chunkid2text[subtree_root_id]
            return text

    def generate_QA(self,sentence):
        self.parsed_sentence = parse(sentence)
        self.dependencies,self.chunkid2text = self._extract_dependencies(self.parsed_sentence)

        qas = list()
        qas += self._agent2what_QA()
        qas += self._aobject_ha2what_QA()
        qas += self._object_wo2what_QA()
        qas += self._object_ga2what_QA()
        qas += self._time2when_QA()
        qas += self._place2where_QA()
        qas += self._source2where_QA()
        qas += self._goal_he2where_QA()
        qas += self._goal_ni2where_QA()
        qas += self._purpose2why_QA()
        return qas

    def _agent2what_QA(self):
        question_and_answers = list()
        target_dependencies = [item for item in self.dependencies if item[2]=='agent']
        for item in target_dependencies:
            target_id = item[1]
            q = ''
            a = ''
            for i in range(len(self.chunkid2text)):
                if i==target_id:
                    q += '何が、'
                    a = self._get_subtree_texts(i)
                elif self._TorF_id_in_subtree_root_id(i,target_id):
                    continue
                else:
                    q += self.chunkid2text[i]
            q += 'か?'
            q = q.replace('。','')
            question_and_answers.append([q,a])
        return question_and_answers
    def _aobject_ha2what_QA(self):
        question_and_answers = list()
        target_dependencies = [item for item in self.dependencies if item[2]=='aobject' \
                               and self.chunkid2text[item[1]][-1]=='は']
        for item in target_dependencies:
            target_id = item[1]
            q = ''
            for i in range(len(self.chunkid2text)):
                if i==target_id:
                    q += '何が、'
                    a = self._get_subtree_texts(i)
                elif self._TorF_id_in_subtree_root_id(i,target_id):
                    continue
                else:
                    q += self.chunkid2text[i]
            q += 'か?'
            q = q.replace('。','')
            question_and_answers.append([q,a])
        return question_and_answers
    def _object_wo2what_QA(self):
        question_and_answers = list()
        target_dependencies = [item for item in self.dependencies if item[2]=='object' \
                               and self.chunkid2text[item[1]][-1]=='を']
        for item in target_dependencies:
            target_id = item[1]
            q = ''
            for i in range(len(self.chunkid2text)):
                if i==target_id:
                    q += '何を'
                    a = self._get_subtree_texts(i)
                elif self._TorF_id_in_subtree_root_id(i,target_id):
                    continue
                else:
                    q += self.chunkid2text[i]
            q += 'か?'
            q = q.replace('。','')
            question_and_answers.append([q,a])
        return question_and_answers
    def _object_ga2what_QA(self):
        question_and_answers = list()
        target_dependencies = [item for item in self.dependencies if item[2]=='object' \
                               and self.chunkid2text[item[1]][-1]=='が']
        for item in target_dependencies:
            target_id = item[1]
            q = ''
            for i in range(len(self.chunkid2text)):
                if i==target_id:
                    q += '何が'
                    a = self._get_subtree_texts(i)
                elif self._TorF_id_in_subtree_root_id(i,target_id):
                    continue
                else:
                    q += self.chunkid2text[i]
            q += 'か?'
            q = q.replace('。','')
            question_and_answers.append([q,a])
        return question_and_answers
    def _time2when_QA(self):
        question_and_answers = list()
        target_dependencies = [item for item in self.dependencies if item[2]=='time']
        for item in target_dependencies:
            target_id = item[1]
            q = ''
            for i in range(len(self.chunkid2text)):
                if i==target_id:
                    q += 'いつ、'
                    a = self._get_subtree_texts(i)
                elif self._TorF_id_in_subtree_root_id(i,target_id):
                    continue
                else:
                    q += self.chunkid2text[i]
            q += 'か?'
            q = q.replace('。','')
            question_and_answers.append([q,a])
        return question_and_answers
    def _place2where_QA(self):
        question_and_answers = list()
        target_dependencies = [item for item in self.dependencies if item[2]=='place' \
                               and self.chunkid2text[item[1]][-1]=='で']
        for item in target_dependencies:
            target_id = item[1]
            q = ''
            for i in range(len(self.chunkid2text)):
                if i==target_id:
                    q += '何処で'
                    a = self._get_subtree_texts(i)
                elif self._TorF_id_in_subtree_root_id(i,target_id):
                    continue
                else:
                    q += self.chunkid2text[i]
            q += 'か?'
            q = q.replace('。','')
            question_and_answers.append([q,a])
        return question_and_answers
    def _source2where_QA(self):
        question_and_answers = list()
        target_dependencies = [item for item in self.dependencies if item[2]=='source' \
                               and self.chunkid2text[item[1]][-2:]=='から']
        for item in target_dependencies:
            target_id = item[1]
            q = ''
            for i in range(len(self.chunkid2text)):
                if i==target_id:
                    q += '何処から'
                    a = self._get_subtree_texts(i)
                elif self._TorF_id_in_subtree_root_id(i,target_id):
                    continue
                else:
                    q += self.chunkid2text[i]
            q += 'か?'
            q = q.replace('。','')
            question_and_answers.append([q,a])
        return question_and_answers
    def _goal_he2where_QA(self):
        question_and_answers = list()
        target_dependencies = [item for item in self.dependencies if item[2]=='goal' \
                               and self.chunkid2text[item[1]][-1]=='へ']
        for item in target_dependencies:
            target_id = item[1]
            q = ''
            for i in range(len(self.chunkid2text)):
                if i==target_id:
                    q += '何処へ'
                    a = self._get_subtree_texts(i)
                elif self._TorF_id_in_subtree_root_id(i,target_id):
                    continue
                else:
                    q += self.chunkid2text[i]
            q += 'か?'
            q = q.replace('。','')
            question_and_answers.append([q,a])
        return question_and_answers
    def _goal_ni2where_QA(self):
        question_and_answers = list()
        target_dependencies = [item for item in self.dependencies if item[2]=='goal' \
                               and self.chunkid2text[item[1]][-1]=='に']
        for item in target_dependencies:
            target_id = item[1]
            q = ''
            for i in range(len(self.chunkid2text)):
                if i==target_id:
                    q += '何に'
                    a = self._get_subtree_texts(i)
                elif self._TorF_id_in_subtree_root_id(i,target_id):
                    continue
                else:
                    q += self.chunkid2text[i]
            q += 'か?'
            q = q.replace('。','')
            question_and_answers.append([q,a])
        return question_and_answers
    def _purpose2why_QA(self):
        question_and_answers = list()
        target_dependencies = [item for item in self.dependencies if item[2]=='purpose']
        for item in target_dependencies:
            target_id = item[1]
            q = ''
            for i in range(len(self.chunkid2text)):
                if i==target_id:
                    q += 'なぜ、'
                    a = self._get_subtree_texts(i)
                elif self._TorF_id_in_subtree_root_id(i,target_id):
                    continue
                else:
                    q += self.chunkid2text[i]
            q += 'か?'
            q = q.replace('。','')
            question_and_answers.append([q,a])
        return question_and_answers

if __name__ == "__main__":
    qa_generator = QAGeneration()
    results = qa_generator.generate_QA('今朝おばあさんが川から拾った桃を切ろうとしたら、桃が突然割れて中から男の子が飛び出してきた')
    for q,a in results:
        print(' Q : ',q)
        print(' A : ',a)
        print()

使ってみる

「今朝おばあさんが川から拾った桃を切ろうとしたら、桃が突然割れて中から男の子が飛び出してきた」

Q :  今朝何が、川から拾った桃を切ろうとしたら、桃が突然割れて中から男の子が飛び出してきたか?
A :  おばあさんが

Q :  今朝おばあさんが川から拾った桃を切ろうとしたら、桃が突然割れて中から何が、飛び出してきたか?
A :  男の子が

Q :  何を切ろうとしたら、桃が突然割れて中から男の子が飛び出してきたか?
A :  今朝おばあさんが川から拾った桃を

Q :  今朝おばあさんが川から拾った桃を切ろうとしたら、何が突然割れて中から男の子が飛び出してきたか?
A :  桃が

Q :  いつ、おばあさんが川から拾った桃を切ろうとしたら、桃が突然割れて中から男の子が飛び出してきたか?
A :  今朝

Q :  今朝おばあさんが何処から拾った桃を切ろうとしたら、桃が突然割れて中から男の子が飛び出してきたか?
A :  川から

Q :  今朝おばあさんが川から拾った桃を切ろうとしたら、桃が突然割れて何処から男の子が飛び出してきたか?
A :  中から

「何を切ろうとしたか?」に対して「今朝おばあさんが川から拾った桃を」などの、構文解析結果を踏まえてまとまったフレーズを回答として抽出できる点が良いところ。
一方で、「何が切ろうとしたか?」「おばあさんが」など、人が回答となる質問にもかかわらず「何」という疑問詞を使う点は改善したいところ。おばあさんが人であることを理解できれば、「誰が」などのwho質問に置き換えられそう。

「おばあさんが川で洗濯をしていると、川上から大きな桃がどんぶらこっこ どんぶらこっこ」

Q :  何が、川で洗濯をしていると、川上から大きな桃がどんぶらこっこ どんぶらこっこか?
A :  おばあさんが

Q :  おばあさんが川で洗濯をしていると、川上から何が、どんぶらこっこ どんぶらこっこか?
A :  大きな桃が

Q :  おばあさんが川で何をしていると、川上から大きな桃がどんぶらこっこ どんぶらこっこか?
A :  洗濯を

Q :  おばあさんが何処で洗濯をしていると、川上から大きな桃がどんぶらこっこ どんぶらこっこか?
A :  川で

Q :  おばあさんが川で洗濯をしていると、何処から大きな桃がどんぶらこっこ どんぶらこっこか?
A :  川上から

質問らしくするために、最後に「か?」を挿入している。
オノマトペで終わる文でも、まあ質問文としては成り立っている…か?

「んでバーが川で洗濯してたら、川上から大きな桃が ジャスティスウェーイ ジャスティスウェーイ」

Q :  んで何が、川で洗濯してたら、川上から大きな桃が ジャスティスウェーイ ジャスティスウェーイか?
A :  バーが

Q :  んでバーが川で洗濯してたら、何が、 ジャスティスウェーイ ジャスティスウェーイか?
A :  川上から大きな桃が

Q :  んでバーが何処で洗濯してたら、川上から大きな桃が ジャスティスウェーイ ジャスティスウェーイか?
A :  川で

Q :  んでバーが川で洗濯してたら、何処から大きな桃が ジャスティスウェーイ ジャスティスウェーイか?
A :  川上から

突然バイブスが上がっても問題なく質問生成できた。
参考:https://news.nicovideo.jp/watch/nw1526903

まとめ

深層格情報をうまく使うことで、自然な文で、質問及び回答生成ができた。
工夫次第でさらに高度な質問も生成できそう。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
92
Help us understand the problem. What are the problem?