UNISIA-SE Tech Blog

気まぐれお勉強日記

[Python] [5] タスク指向型対話システム (フレームベースの環境準備 : SVM(sklearn)のモデル学習実装サンプル)

1. 前提条件


フレームベースのタスク指向型対話システムを作るために使用する SVM(sklearn) のモデル学習実装サンプルについて記載する。

以下、必要な前提知識。

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

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

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

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

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

2. モデル学習の実装サンプル


以下、前ページで作成した学習データ「da_samples.dat」(※1)を MeCab(※2) で最小単位の単語(形態素)に分割し、SVM(※3) で対話行為タイプを推定(モデル学習)する実装サンプル。

train_da_model.py

import MeCab
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.svm import SVC
from sklearn.preprocessing import LabelEncoder
import dill

# MeCabの初期化
mecab = MeCab.Tagger()
mecab.parse('')

sents = []
labels = []

# generate-samples.txt の出力である samples.dat の読み込み
for line in open("da_samples.dat","r"):
    line = line.rstrip()
    # samples.dat は発話行為タイプ,発話文,タグとその文字位置が含まれている
    da, utt = line.split('\t')
    words = []
    for line in mecab.parse(utt).splitlines():
        if line == "EOS":
            break
        else:
            # MeCabの出力から単語を抽出
            word, feature_str = line.split("\t")
            words.append(word)
    # 空白区切りの単語列をsentsに追加
    sents.append(" ".join(words))
    # 発話行為タイプをlabelsに追加
    labels.append(da)

# TfidfVectorizerを用いて,各文をベクトルに変換
vectorizer = TfidfVectorizer(tokenizer=lambda x:x.split(), ngram_range=(1,3))
X = vectorizer.fit_transform(sents)

# LabelEncoderを用いて,ラベルを数値に変換
label_encoder = LabelEncoder()
Y = label_encoder.fit_transform(labels)

# SVMでベクトルからラベルを取得するモデルを学習
svc = SVC(gamma="scale")
svc.fit(X,Y)

# 学習されたモデル等一式を svc.modelに保存
with open("svc.model","wb") as f:
    dill.dump(vectorizer, f)
    dill.dump(label_encoder, f)
    dill.dump(svc, f)

以下、処理解説。
※ 機械学習では、推定対象となるものを ラベルクラス と呼ぶ。
今回のデータでは、発話行為タイプの request-weather がラベルとなるため、コメントや変数名の表現もラベルとなっている。

▶ 学習データ「da_samples.dat」を読込み、それぞれの行に対して下記要領で発話文字列を単語分割した sents と発話行為タイプ  labels をそれぞれ作成する。
(1) 行の末尾の空白を削除し(rstrip)、タブで分割(split)する。
(2) 1つ目分割結果(発話行為タイプ)を da に、2つ目分割結果(発話文字列)を utt に格納する。
(3) mecabで最小単位の単語(形態素)に分割し(splitlines)、最後(EOS)であればループを終了、最後(EOS)でなければタブで分割(split)し、先頭単語(word)のみを単語配列(words)に追加する。
(4) 単語配列(words)を空白区切りに置換え sents に追加する。
(5) 発話行為タイプ(da)を labels に追加する。

for line in open("da_samples.dat","r"):
    line = line.rstrip()
    # samples.dat は発話行為タイプ,発話文,タグとその文字位置が含まれている
    da, utt = line.split('\t')
    words = []
    for line in mecab.parse(utt).splitlines():
        if line == "EOS":
            break
        else:
            # MeCabの出力から単語を抽出
            word, feature_str = line.split("\t")
            words.append(word)
    # 空白区切りの単語列をsentsに追加
    sents.append(" ".join(words))
    # 発話行為タイプをlabelsに追加
    labels.append(da)

▶ 上記で取得した発話内容「sents」と発話行為タイプ「labels」の関連付けを学習させるために発話情報を数値列に変換する必要があり、その作成には TfidfVectorizerを用いて変換する。

これを 素性ベクトル と呼び、後続処理で ラベル との関連付け学習を行うために作成している。
fit_transformで発話内容「sents」を素性ベクトルに変換したものが X で、発話情報にある単語の登場頻度と学習データから推測できる単語の重要度をもとに素数ベクトルを自動生成している。

# TfidfVectorizerを用いて,各文をベクトルに変換
vectorizer = TfidfVectorizer(tokenizer=lambda x:x.split(), ngram_range=(1,3))
X = vectorizer.fit_transform(sents)

▶ 上記と同様にfit_transformで発話行為タイプ「labels」(ラベル)を数値列に変換したものを Y に保持している。

# LabelEncoderを用いて,ラベルを数値に変換
label_encoder = LabelEncoder()
Y = label_encoder.fit_transform(labels)

▶ コメントの通り、発話内容「sents」を素性ベクトル化した X から、発話行為タイプ「labels」(ラベル)を数値列化した Y を取得するモデルを SVM を用いて学習させている。
モデル とは素性ベクトルの各要素とラベルとの関連を定義した大量の通知データのこと指す。

# SVMでベクトルからラベルを取得するモデルを学習
svc = SVC(gamma="scale")
svc.fit(X,Y)

▶ 最後に素性ベクトル作成時に用いた vectorizer とラベルを数値列化する際に用いた label_encoder、そしてモデル学習時に用いた svc 達を dill を用いて、ファイルに出力する。

# 学習されたモデル等一式を svc.modelに保存
with open("svc.model","wb") as f:
    dill.dump(vectorizer, f)
    dill.dump(label_encoder, f)
    dill.dump(svc, f)

▶ 上記実装サンプル(train_da_model.py)の実行確認
※ 実行後 svc.model が生成されていれば成功。

$ python ~/gitlocalrep/dsbook/train_da_model.py

3. 学習結果の確認


前項で作成した svc.model が正しく推定できるか以下のテストプログラムを実行して確認する。

da_extractor.py(テストプログラム)

import MeCab
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.svm import SVC
from sklearn.preprocessing import LabelEncoder
import dill

mecab = MeCab.Tagger()
mecab.parse('')

# SVMモデルの読み込み
with open("svc.model","rb") as f:
    vectorizer = dill.load(f)
    label_encoder = dill.load(f)
    svc = dill.load(f)

# 発話から発話行為タイプを推定  
def extract_da(utt):
    words = []
    for line in mecab.parse(utt).splitlines():
        if line == "EOS":
            break
        else:
            word, feature_str = line.split("\t")
            words.append(word)
    tokens_str = " ".join(words)
    X = vectorizer.transform([tokens_str])
    Y = svc.predict(X)
    # 数値を対応するラベルに戻す
    da = label_encoder.inverse_transform(Y)[0]
    return da

for utt in ["大阪の明日の天気","もう一度はじめから","東京じゃなくて"]:
    da = extract_da(utt)
    print(utt,da)

以下、テストプログラム(da_extractor.py)の処理解説。
▶ まず svc.model を開き、上記で出力した vectorizerlabel_encodersvcdill を用いて読込む。

# SVMモデルの読み込み
with open("svc.model","rb") as f:
    vectorizer = dill.load(f)
    label_encoder = dill.load(f)
    svc = dill.load(f)

▶ 発話行為タイプを推定する extract_da メソッド。
下記の要領で推定結果 da を返す。
(1) mecabで最小単位の単語(形態素)に分割し(splitlines)、最後(EOS)であればループを終了、最後(EOS)でなければタブで分割(split)し、先頭単語(word)のみを単語配列(words)に追加する。
(2) 単語配列(words)を空白区切りに置換え tokens_str に格納する。
(3) 上記で読込んだ vectorizerTfidfVectorizer を用いて素性ベクトルに変換する。
(4) (3)X と上記で読込んだ svcpredict を用いて推定結果を取得する。
(5) 上記で読込んだ label_encoder をもとに数値列からラベル名に戻して返す。

  # 発話から発話行為タイプを推定  
def extract_da(utt):
    words = []
    for line in mecab.parse(utt).splitlines():
        if line == "EOS":
            break
        else:
            word, feature_str = line.split("\t")
            words.append(word)
    tokens_str = " ".join(words)
    X = vectorizer.transform([tokens_str])
    Y = svc.predict(X)
    # 数値を対応するラベルに戻す
    da = label_encoder.inverse_transform(Y)[0]
    return da  

▶ テスト用の3つの発話内容で上記 extract_da メソッドを呼び、発話行為タイプの推定結果を取得する。

for utt in ["大阪の明日の天気","もう一度はじめから","東京じゃなくて"]:
    da = extract_da(utt)
    print(utt,da)

▶ 上記テストプログラム(da_extractor.py)の実行結果
それぞれの発話内容(左)に対して、正しい発話行為タイプの推定(右)ができている。

$ python ~/gitlocalrep/dsbook/da_extractor.py
大阪の明日の天気 request-weather
もう一度はじめから initialize
東京じゃなくて correct-info

以上。
そして、ここで「タスク指向型対話システム」の連投は、一旦終了。


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


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