SIGMA-SE Math & Tech Library

SIGMA-SE Math & Tech Library


数学と情報技術をテーマに、書籍や教材だけではつかみにくい考え方を具体例とともに簡潔にわかりやすく伝える解説サイトです。
技術の歴史や背景、関連知識の整理、学習のための覚書や要約記事も掲載しています。

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

概要

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

この記事の構成

対象環境と利用上の注意

  • 本文記載時の環境
    CentOS \(7\)、Python \(3.6\)系、当時のscikit-learn・MeCab・dillを前提としたモデル学習・保存例。
  • 確認時期
    2026年8月にscikit-learn公式資料と照合し、モデル評価とモデル永続化の注意を見直した。
  • 現在そのまま利用できない箇所
    掲載コード全体は現行環境で再実行していない。
    旧環境をそのまま新規利用せず、依存版を固定し、信頼できるモデルファイルだけを読み込み、学習データと分離した評価データで性能を確認する。

解説と実装サンプル

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

  • TF-IDF・SVMモデルの学習

    先行記事で作成した学習データ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)
    

    dillpickle形式の読込みは任意のコードを実行し得るため、自分で生成した信頼できるファイルだけを読み込む。また、scikit-learnは異なるバージョン間でのモデル読込みを保証しないため、Pythonと依存ライブラリのバージョン、学習コード、データの識別情報も一緒に記録する。

    以下、処理解説。
    ※ 機械学習では、推定対象となるものをラベルクラスと呼ぶ。
    今回のデータでは、発話行為タイプの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
    

    分類性能を評価する場合は、学習に使っていないデータを用意し、正解率だけでなく適合率、再現率、混同行列などを確認する。同じテンプレートから生成した類似文が学習用と評価用の両方へ入ると、実際より高く評価されるため、元テンプレート単位で分離する。

まとめ

  • MeCabで発話を単語分割し、TF-IDFで発話内容を素性ベクトルXへ変換。発話行為タイプはラベルYとして扱う。
  • SVMは発話ベクトルから発話行為タイプを分類。
  • 推定時にも学習時と同じ変換と分類を再現できるよう、vectorizer、label_encoder、svcをセットで保存して読み込む。
  • 掲載した3文の推定は疎通確認であり、モデルの性能評価には独立した評価データと評価指標が必要。
  • 学習用の例文が偏ると誤分類しやすくなるため、発話行為タイプごとのデータ内容と件数を確認。

参考文献

この記事を共有
Xで共有 Facebookで共有 LINEで共有



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