SIGMA-SE Tech Blog

SIGMA-SE Tech Blog

Python - タスク指向型対話 : フレームベースのSVM(sklearn)モデル学習実装サンプル

目的

この記事では、フレームベースのタスク指向型対話システムを作るために使用する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を開き、上記で出力した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
    

参考文献

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

GitHubサポートページ



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