目的
この記事では、フレームベースのタスク指向型対話システムを作るために使用するSVM(sklearn)のモデル学習実装サンプルについて記載する。
解説と実装サンプル
モデル学習の実装サンプル
先行記事で作成した学習データda_samples.dat
(*1)をMeCab(*2)で最小単位の単語(形態素)に分割し、SVM(*3)で対話行為タイプを推定(モデル学習)する実装サンプル。
-
(*1)Python - タスク指向型対話 : フレームベースの環境準備 > SVM(sklearn)と学習データの作成 > 学習データの作成
-
(*2)Python - タスク指向型対話 : 状態遷移ベースの環境準備 > MeCab, SCXML > 対話の文章を解析する「MeCab」の概要とインストール
-
(*3)Python - タスク指向型対話 : フレームベースの環境準備 > SVM(sklearn)と学習データの作成 > フレームと対話行為を推定するSVM(sklearn)の概要とインストール
-
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
を開き、上記で出力したvectorizer
、label_encoder
、svcをdillを用いて読み込む。# 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)上記で読込んだ
vectorizer
をTfidfVectorizer
を用いて素性ベクトルに変換する。 -
(*12)(*11)の
X
と上記で読み込んだsvcのpredict
を用いて推定結果を取得する。 -
(*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
参考文献
- 東中 竜一郎、稲葉 通将、水上 雅博 (2020) 『Pythonでつくる対話システム』株式会社オーム社