SIGMA-SE Tech Blog

SIGMA-SE Tech Blog


当サイトは、過去に運営していた別ドメイン(unisia-se.com)から sigma-se.com へ移行した技術ブログです。
旧サイトの記事をもとに、内容の精査・加筆・最新化を行い再構成しています。
正確で実用的な情報提供を目的としています。

Python - タスク指向型対話:5/5 SVMモデル学習と発話行為推定

概要

フレームベースのタスク指向型対話システムで使うSVMモデルを学習し、発話行為タイプを推定する流れを整理する。
前の記事で作成した学習データをMeCabで単語分割し、TF-IDFで数値ベクトルに変換し、SVMで発話行為タイプを分類するモデルを作成する。
ここでは、モデル学習、モデル保存、推定用プログラム、実行結果の確認までを順番に見る。

この記事で扱うこと

  • MeCabで発話を単語列に変換する流れ。
  • TfidfVectorizerで発話を素性ベクトル化する方法。
  • LabelEncoderでラベルを数値化する理由。
  • SVMモデルを保存し、別プログラムで読み込んで推定する流れ。

作業前に確認すること

確認項目 内容
学習データpid42で作成したda_samples.datを用意する。
ライブラリMeCab、scikit-learn、dillを使える状態にする。
確認観点学習、保存、読み込み、推定を分けて確認する。

注意したい点

注意したい点 確認する観点
素性ベクトルとラベルの混同Xは発話内容の数値表現、Yは発話行為タイプの数値表現。
モデル保存の中身vectorizer、label_encoder、svcをセットで保存しないと推定時に同じ変換ができない。
学習データの品質例文が偏ると、正しくない発話行為タイプを推定しやすくなる。

解説と実装サンプル

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

先行記事で作成した学習データ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がラベルとなるため、コメントや変数名の表現もラベルとなっている。

  • sentsとlabelsの作成

学習データda_samples.datを読み込み、それぞれの行に対して下記要領で発話文字列を単語分割したsentsと発話行為タイプlabelsをそれぞれ作成する。

  • (*4)行の末尾の空白を削除し(rstrip)、タブで分割(split)する。
  • (*5)1つ目分割結果(発話行為タイプ)をdaに、2つ目分割結果(発話文字列)をuttに格納する。
  • (*6)mecabで最小単位の単語(形態素)に分割し(splitlines)、最後(EOS)であればループを終了、最後(EOS)でなければタブで分割(split)し、先頭単語(word)のみを単語配列(words)に追加する。
  • (*7)単語配列(words)を空白区切りに置き換えsentsに追加する。
  • (*8)発話行為タイプ(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)
  • 変換後の数値列をYに保持

上記と同様に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)
  • svc.modelに保存

最後に素性ベクトル作成時に用いた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

学習結果の確認

前項で作成した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)の処理解説。

  • SVMモデルの読み込み

まず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を返す。

  • (*9)mecabで最小単位の単語(形態素)に分割し(splitlines)、最後(EOS)であればループを終了、最後(EOS)でなければタブで分割(split)し、先頭単語(word)のみを単語配列(words)に追加する。
  • (*10)単語配列(words)を空白区切りに置き換え tokens_strに格納する。
  • (*11)上記で読み込んだvectorizerTfidfVectorizerを用いて素性ベクトルに変換する。
  • (*12)(*11)のXと上記で読み込んだsvcpredictを用いて推定結果を取得する。
  • (*13)上記で読み込んだ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

実務とのつながり

  • 意図分類の基本
    問い合わせ分類、チャットBot、FAQルーティングなどでは、発話から意図を推定する処理が重要になる。
  • 推論環境への展開
    学習時に使った前処理器とモデルを一緒に保存しておくと、別プログラムで再利用しやすい。

まとめ

  • MeCabで発話を単語分割し、TF-IDFで数値ベクトルに変換する。
  • SVMは発話ベクトルから発話行為タイプを分類する。
  • 推定時には、学習時と同じvectorizer、label_encoder、svcを読み込む必要がある。

参考文献

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

GitHubサポートページ



Copyright SIGMA-SE All Rights Reserved.
s-hama@sigma-se.jp