UNISIA-SE Tech Blog

気まぐれお勉強日記

[Python] [4] タスク指向型対話システム (フレームベースの環境準備:SVM(sklearn))

1. 前提条件


フレームベースのタスク指向型対話システムを作るために使用する SVM(sklearn) の概要及びインストールと学習データの作成ついて記載する。

以下、必要な前提知識。

▼ 必要に応じて下記ページを参考に環境を準備すること。
[Python] [1] タスク指向型対話システム (状態遷移ベースの環境準備:MeCab, SCXML)
[Python] [2] タスク指向型対話システム (状態遷移ベースの環境準備:OpenWeatherMap, Telegram)
[Python] [3] タスク指向型対話システム (状態遷移ベースの実装)

▼ Python3.6がインストールされていること。
このページでは、venvの仮想環境(Python3.6)上にNumPyをインストールした環境で、Python対話モード(Pythonインタプリタ)にて実装サンプルを記載している。

※ Python対話モードについては下記を参考。
Python対話モード:[Python] 対話モード (インタプリタ) の使用方法

▼ 対話システムのプログラムについて
この記事で登場するサンプルプログラムは、下記参考文献『Pythonでつくる対話システム』のGitHubサポートページで提供されているプログラムをダウンロードして使用させていただく。

【参考文献】
東中 竜一郎、稲葉 通将、水上 雅博 (2020) 『Pythonでつくる対話システム』株式会社オーム社
【GitHubサポートページ】
https://github.com/dsbook/dsbook

2. タスク指向型対話システム (フレームベース) のシステム構成について


システム構成は、先行ページの状態遷移ベースと同様に Pythonで作成する対話システムTelegramサーバーTelegramをインストールしたクライアント端末(Android、iPhone、Windows、Linux、Macなど) の単純な 3構成 でデータベースも使わない。
※ 以降、Telegramインストールした端末を Telegramクライアント と表現する。

大まかな処理の流れとしては、 Pythonで作成する対話システム を起動すると、 これが Bot として Telegramクライアント に表示され、ユーザー要求の入力待ち状態となる。

そして、ユーザーが要求(入力)すると Telegramサーバー を介し、対話システム(Bot) の応答制御処理を経て、結果をユーザーに返し、対話していく。

上記の前提と重複するがこのページでは、SVM(sklearn) の概要とインストールと学習データ作成ついて解説する。

また、次ページ以降も継続して解説していく、 タスク指向型システムのフレームベースについての動作環境は、先行ページの状態遷移ベースと同じで Telegramクライアント が Windows、Pythonの対話システム が Linux(CentOS7) となるので、コマンドは特に自身の環境に合わせて読替えること。

3. フレームと対話行為を推定する「SVM(sklearn)」の概要とインストール


まず、フレーム とは、一度に複数の情報を受付けられるように スロット と呼ばれる 属性 からなる入力情報を複数持つデータ構造のことで、遂行手法としてフレームベースとも呼ばれる。

フレームベースの基本処理構成は、発話処理部対話管理部(状態更新、行動決定)、発話生成部から成っていて、パイプラインアーキテクチャ または、Vモデル とも呼ばれる。
※ このページでは、フレームベースに関する解説は概要レベルに留め、次ページ以降でSVMやCRFを絡めた実装を例に掘り下げて解説する。

始めのユーザーの発話直後に処理される 発話処理部 では、発話内容から 発話の意図となる対話行為 を推定する。

対話行為 は、対話の大分類(意図)を表す 対話行為タイプ とその詳細情報である コンセプト で構成される。

例えば、ユーザーの入力値が「福岡の明日の天気は?」の場合
発話行為タイプを「天気情報の要求」に分類し、コンセプトの場所(都道府県)を「福岡」、日付を「明日」、情報種別を「天気」という具合に分類(推定)する。

対話行為タイプコンセプト は、開発環境(※ 1、※ 2)によって呼び方が違うので注意。
※ 1「Google Home」は、対話行為タイプインテントコンセプトエンティティ と呼ぶ。
※ 2「Amazon Alexa」は、対話行為タイプインテントコンセプトスロット と呼ぶ。
(上記フレームのスロット(属性/値)と※2のスロットは表現は同じ「スロット」という表現だが意味が違うので混同注意。)

この 対話行為タイプ を推定するためにパターン識別用の機械学習方法の一つである SVM(support vector machine) を使用する。
※ パターン識別の理屈については、ここで触れませんがデータを2つのグループに分類する問題に優れており、2分類化することにおいては、最も優秀な識別能力を持つとされる。

SVMによる学習は、Pythonの機会学習ライブラリである sklearn(scikit-learn) を用いて実装する。

▶ sklearn のインストール

$ pip3 install sklearn

また、学習結果を保存するため シリアライズモジュールである dill もインストールする。

$ pip3 install dill

以上がSVMの環境準備。

また、対話行為の分類基準を確立するため、学習データ(事例)が必要になってくるため、次項で作成方法を解説する。

4. 学習データの作成


この案内対話で使用する発話行為タイプとコンセプトを以下のように定義する。

発話行為タイプ
発話行為名 発話行為キー 備考
天気情報の要求requet-weatherユーザーが天気情報を要求している。
伝達情報の初期化initializeユーザーが案内の初期化を要求している。
伝達情報の訂正correct-infoユーザーが発話行為の推定を訂正要求している。

コンセプト
属性名 属性キー
場所(都道府県)place都道府県のいずれか
日付date今日、明日等の日付
情報種別type天気 or 気温

例えば、上記発話行為タイプの学習データを作成する場合
前方を「発話行為キー」、後方を「ユーザーの入力値」とし、そのまま愚直に書くと

 requet-weather 福岡の天気は?
 requet-weather 明日の福岡の気温教えて
 correct-info 大阪じゃなくて福岡です
 correct-info 天気じゃなく気温
 initialize もう一度はじめから
 initialize リセットして!
 …

という具合に発話行為タイプ別の都道府県別でさらにニュアンスを変えた学習データを一つ一つ手動で書かなけれならず、現実的でないため、タグ付けした学習データを作り、これを量産するプログラムを準備する。

まず、学習データにコンセプトの属性キーでタグを付けた代表データ(examples.txt)なるものを準備する。
※ da=XXXX でどの発話行為タイプのデータか定義している。

examples.txt

da=request-weather
大阪
大阪です
明日
明日です
天気
天気です
大阪明日
大阪明日です
大阪天気
大阪天気です
大阪天気を教えてください
明日天気
明日天気です
明日天気を教えてください
明日大阪天気
明日大阪天気です
明日大阪天気を教えてください
大阪明日天気
大阪明日天気です
大阪明日天気を教えてください

da=initialize
もう一度はじめから
はじめから
はじめからお願いします
最初から
最初からお願いします
初期化してください
キャンセル
すべてキャンセル

da=correct-info
大阪じゃない
大阪じゃなくて
大阪じゃないです
大阪ではありません
明日じゃない
明日じゃなくて
明日じゃないです
明日ではありません
天気じゃない
天気じゃなくて
天気じゃないです
天気ではありません

次に上記代表データを読込んで対象のタグに応じた辞書(都道府県名、日付、情報種別)からランダムで一つ抽出し、学習データを作成するプログラム(generate_da_samples.py)を準備する。
※ 下記のプログラムでは、代表データ一行あたり1000個の学習データを作成している。

generate_da_samples.py

import re
import random
import json
import xml.etree.ElementTree

# 都道府県名のリスト
prefs = ['三重', '京都', '佐賀', '兵庫', '北海道', '千葉', '和歌山', '埼玉', '大分',
         '大阪', '奈良', '宮城', '宮崎', '富山', '山口', '山形', '山梨', '岐阜', '岡山',
         '岩手', '島根', '広島', '徳島', '愛媛', '愛知', '新潟', '東京',
         '栃木', '沖縄', '滋賀', '熊本', '石川', '神奈川', '福井', '福岡', '福島', '秋田',
         '群馬', '茨城', '長崎', '長野', '青森', '静岡', '香川', '高知', '鳥取', '鹿児島']

# 日付のリスト
dates = ["今日","明日"]

# 情報種別のリスト
types = ["天気","気温"]

# サンプル文に含まれる単語を置き換えることで学習用事例を作成
def random_generate(root):
    buf = ""
    # タグがない文章の場合は置き換えしないでそのまま返す
    if len(root) == 0:
        return root.text
    # タグで囲まれた箇所を同じ種類の単語で置き換える
    for elem in root:
        if elem.tag == "place":
            pref = random.choice(prefs)
            buf += pref
        elif elem.tag == "date":
            date = random.choice(dates)
            buf += date
        elif elem.tag == "type":
            _type =  random.choice(types)
            buf += _type
        if elem.tail is not None:
            buf += elem.tail
    return buf

# 学習用ファイルの書き出し先
fp = open("da_samples.dat","w")

da = ''
# examples.txt ファイルの読み込み
for line in open("examples.txt","r"):
    line = line.rstrip()
    # da= から始まる行から対話行為タイプを取得
    if re.search(r'^da=',line):
        da = line.replace('da=','')
    # 空行は無視
    elif line == "":
        pass
    else:
        # タグの部分を取得するため,周囲にダミーのタグをつけて解析
        root = xml.etree.ElementTree.fromstring(""+line+"")
        # 各サンプル文を1000倍に増やす
        for i in range(1000):
            sample = random_generate(root)
            # 対話行為タイプ,発話文,タグとその文字位置を学習用ファイルに書き出す
            fp.write(da + "\t" + sample + "\n")

fp.close()


以下、上記 generate_da_samples.py の処理解説(実行順)。

▶ 都道府県名、日付、情報種別の辞書を定義する。

… (省略) …
# 都道府県名のリスト
prefs = ['三重', '京都', '佐賀', '兵庫', '北海道', '千葉', '和歌山', '埼玉', '大分',
         '大阪', '奈良', '宮城', '宮崎', '富山', '山口', '山形', '山梨', '岐阜', '岡山',
         '岩手', '島根', '広島', '徳島', '愛媛', '愛知', '新潟', '東京',
         '栃木', '沖縄', '滋賀', '熊本', '石川', '神奈川', '福井', '福岡', '福島', '秋田',
         '群馬', '茨城', '長崎', '長野', '青森', '静岡', '香川', '高知', '鳥取', '鹿児島']

# 日付のリスト
dates = ["今日","明日"]

# 情報種別のリスト
types = ["天気","気温"]
… (省略) …

▶ 作成する学習用データファイル「da_samples.dat」を新規作成し、書込みモードで開いた fp を定義する。
※ openは、指定したファイルが存在しなければ新規作成した状態で開き、存在していれば上書き状態で開く。
但し、指定したファイルがおかれているディレクトリが存在しない場合は、エラーとなるので注意。

… (省略) …
# 学習用ファイルの書き出し先
fp = open("da_samples.dat","w")
… (省略) …

▶ 上記で準備した代表データ(examples.txt)を読込みモードで開き、読込んだ行数分ループし (1) or (2) or (3) を実行する。
(1) 正規表現モジュール reda= から始まるかどうかチェックし、始まるのであれば、右辺の発話行為タイプを da に保持する。
(2) 空白行とみなしスキップし、何もしない。
(3) XMLとして解析するため(最上位のタグは1つでなければならない)、対象行を dummy タグで囲い、fromstringで文字列から xml.etree.ElementTree.Element にパースしたものを root に保持する。

この root を引数に1000回 random_generate メソッド(処理詳細は下記 ※ 3 を参照)を呼出し、対話行為タイプと random_generate の戻り値 samplefpに書込む。

… (省略) …
da = ''
# examples.txt ファイルの読み込み
for line in open("examples.txt","r"):
    line = line.rstrip()
    # da= から始まる行から対話行為タイプを取得
    if re.search(r'^da=',line):
        da = line.replace('da=','')
    # 空行は無視
    elif line == "":
        pass
    else:
        # タグの部分を取得するため,周囲にダミーのタグをつけて解析
        root = xml.etree.ElementTree.fromstring(""+line+"")
        # 各サンプル文を1000倍に増やす
        for i in range(1000):
            sample = random_generate(root)
            # 対話行為タイプ,発話文,タグとその文字位置を学習用ファイルに書き出す
            fp.write(da + "\t" + sample + "\n")
… (省略) …

▶ ※ 3 random_generate メソッドの処理
引数の root 内にタグが存在しない場合(len(root) == 0)、root直下のテキストをそのまま返す。
以降は、root 内のタグに応じて、都道府県名 or 日付 or 情報種別の辞書からそれぞれからランダム抽出した結果と対象タグ末尾の発話文(tail)を buf に加算して返す。

… (省略) …
# サンプル文に含まれる単語を置き換えることで学習用事例を作成
def random_generate(root):
    buf = ""
    # タグがない文章の場合は置き換えしないでそのまま返す
    if len(root) == 0:
        return root.text
    # タグで囲まれた箇所を同じ種類の単語で置き換える
    for elem in root:
        if elem.tag == "place":
            pref = random.choice(prefs)
            buf += pref
        elif elem.tag == "date":
            date = random.choice(dates)
            buf += date
        elif elem.tag == "type":
            _type =  random.choice(types)
            buf += _type
        if elem.tail is not None:
            buf += elem.tail
    return buf
… (省略) …

▶ 最後に fp を閉じて処理終了。

… (省略) …
fp.close()
… (省略) …

最後に実行確認。

▶ generate_da_samples.pyの実行確認。
上記の代表データ(examples.txt)が generate_da_samples.py と同じフォルダ内にある状態で実行する。
(もちろん generate_da_samples.py までのファイルパスは各自環境と読替えること。)

$ python ~/gitlocalrep/dsbook/generate_da_samples.py

▶ da_samples.datの確認
代表データ(examples.txt)の学習データが40個でそれぞれ1000個の学習データを出力ているため、da_samples.dat は、40000個のデータとなっている。

$ cat ~/gitlocalrep/dsbook/da_samples.dat
request-weather 高知
request-weather 大分
request-weather 京都
… (省略) …
equest-weather 青森です
request-weather 神奈川です
request-weather 神奈川です
… (省略) …
request-weather 今日
request-weather 明日
request-weather 今日
… (省略) …
initialize もう一度はじめから
initialize はじめから
initialize はじめからお願いします
… (省略) …
correct-info 福岡じゃない
correct-info 神奈川じゃない
correct-info 広島じゃない
… (省略) …

以上で学習データを準備が完了。


【参考文献】
東中 竜一郎、稲葉 通将、水上 雅博 (2020) 『Pythonでつくる対話システム』株式会社オーム社


Copyright UNISIA-SE All Rights Reserved.
s-hama@unisia-se.jp