SIGMA-SE Math & Tech Library

SIGMA-SE Math & Tech Library


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

Python - タスク指向型対話:4/5 フレームベースの環境準備(SVM・学習データ)

概要

フレームベースのタスク指向型対話システムに向けて、SVM(scikit-learn)の役割と学習データの作成手順を整理する。

フレームベースでは、発話から対話行為タイプや必要なスロット情報を推定し、対話管理に渡す。状態遷移ベースより柔軟な入力を扱いやすくなる一方で、学習データの作り方が重要になる。

ここでは、SVMの準備、発話行為タイプとコンセプトの定義、学習データ生成の流れを確認する。

この記事の構成

対象環境と利用上の注意

  • 本文記載時の環境
    CentOS \(7\)、Python \(3.6\)系、当時のscikit-learn・MeCab・dillを前提とした学習用実装。
  • 確認時期
    2026年8月にscikit-learn公式資料と照合し、インストール方法、モデル評価、保存時の注意を見直した。
  • 現在そのまま利用できない箇所
    データ生成・学習コード全体は現行環境で再実行していない。
    旧OSや未固定の旧ライブラリをそのまま新規利用せず、現行環境では依存版を固定し、学習用・評価用データを分離して結果を検証する。

作業時の注意点

  • フレームと状態の違い
    状態遷移は流れを管理し、フレームは発話から埋める情報構造として考える。
  • 学習データの偏り
    例文が少ない、表現が偏ると、推定結果も偏りやすい。
  • 用語の違い
    インテント、エンティティ、スロットなどはサービスによって呼び方が異なる。

実装内容

タスク指向型対話システム(フレームベース)のシステム構成について

  • 構成要素と処理の流れ

    システム構成は、状態遷移ベースと同様にPythonで作成する対話システムTelegramサーバーTelegramをインストールしたクライアント端末(Android、iPhone、Windows、Linux、Macなど)の単純な3構成でデータベースも使わない。
    ※ 以降、Telegramインストールした端末をTelegramクライアントと表現する。

    大まかな処理の流れとしては、Pythonで作成する対話システムを起動すると、これがBotとしてTelegramクライアントに表示され、ユーザー要求の入力待ち状態となる。

    その後、ユーザーが要求(入力)するとTelegramサーバーを介し、対話システム(Bot)の応答制御処理を経て、結果をユーザーに返し対話していく。

    ここからは、SVM(sklearn)の概要、インストール、学習データ作成を確認する。

    また、次の記事以降も継続して解説していく、タスク指向型システムのフレームベースについての動作環境は、状態遷移ベースと同じでTelegramクライアントがWindows、Pythonの対話システムがLinux(CentOS7)となるので、コマンドは特に自身の環境に合わせて読み替えること。

フレームと対話行為を推定するSVM(sklearn)の概要とインストール

  • SVMによる発話行為推定

    まず、フレームとは、一度に複数の情報を受付けられるようにスロットと呼ばれる属性からなる入力情報を複数持つデータ構造のことで、遂行手法としてフレームベースとも呼ばれる。

    フレームベースの基本処理構成は、発話処理部対話管理部(状態更新、行動決定)、発話生成部を順に接続するパイプラインとして表せる。ソフトウェア開発工程を表すVモデルとは別の概念となる。
    ※ ここでは、フレームベースに関する解説は概要レベルに留め、次の記事以降でSVMやCRFを絡めた実装を例に掘り下げていく。

    始めのユーザーの発話直後に処理される発話処理部では、発話内容から発話の意図となる対話行為を推定する。

    対話行為は、対話の大分類(意図)を表す対話行為タイプとその詳細情報であるコンセプトで構成される。

    例えば、ユーザーの入力値が「福岡の明日の天気は?」の場合 発話行為タイプを「天気情報の要求」に分類し、コンセプトの場所(都道府県)を「福岡」、日付を「明日」、情報種別を「天気」という具合に分類(推定)する。

    対話行為タイプとコンセプトは、開発環境(*1)、(*2)によって呼び方が違うので注意。

    • (*1)「Google Home」は、対話行為タイプをインテント、コンセプトをエンティティと呼ぶ。
    • (*2)「Amazon Alexa」は、対話行為タイプをインテント、コンセプトをスロットと呼ぶ。
      (上記フレームのスロット(属性/値)と(*2)のスロットは表現は同じ「スロット」という表現だが意味が違うので混同注意。)

    この対話行為タイプを推定するためにパターン識別用の機械学習方法の一つであるSVM(support vector machine)を使用する。
    ※ SVMは2値分類を基本とするが、scikit-learnのSVCは複数の2値分類器を組み合わせて多クラス分類にも対応する。どの分類器が最も良いかは、データ量、特徴量、評価指標などによって異なる。

    SVMによる学習は、Pythonの機械学習ライブラリであるsklearn(scikit-learn)を用いて実装する。

    • scikit-learn、dillのインストール

      $ pip3 install scikit-learn
      

      また、学習結果を保存するため シリアライズモジュールであるdillもインストール。

      $ pip3 install dill
      

    以上がSVMの環境準備。

    また、対話行為の分類基準を確立するため、学習データ(事例)が必要になってくるため、次項で作成方法を解説する。

学習データの作成

  • 教師データの生成

    この案内対話で使用する発話行為タイプとコンセプトを以下のように定義する。

    • 発話行為タイプ

      発話行為名 発話行為キー 備考
      天気情報の要求 request-weather ユーザーが天気情報を要求している。
      伝達情報の初期化 initialize ユーザーが案内の初期化を要求している。
      伝達情報の訂正 correct-info ユーザーが発話行為の推定を訂正要求している。
    • コンセプト

      属性名 属性キー
      場所(都道府県) place 都道府県のいずれか
      日付 date 今日、明日等の日付
      情報種別 type 天気 or 気温

    例えば、上記発話行為タイプの学習データを作成する場合
    前方を「発話行為キー」、後方を「ユーザーの入力値」とし、そのまま愚直に書くと

    request-weather 福岡の天気は?
    request-weather 明日の福岡の気温教えて
    correct-info 大阪じゃなくて福岡です
    correct-info 天気じゃなく気温
    initialize もう一度はじめから
    initialize リセットして!
    …
    

    という具合に発話行為タイプ別の都道府県別でさらにニュアンスを変えた学習データを一つ一つ手動で書かなければならず、現実的でないため、タグ付けした学習データを作り、これを量産するプログラムを準備する。

    まず、学習データにコンセプトの属性キーでタグを付けた代表データ(examples.txt)なるものを準備する。
    ※ da=XXXX でどの発話行為タイプのデータか定義している。

    • examples.txt
      da=request-weather
      大阪
      大阪です
      明日
      明日です
      天気
      天気です
      大阪の明日
      大阪の明日です
      大阪の天気
      大阪の天気です
      大阪の天気を教えてください
      明日の天気
      明日の天気です
      明日の天気を教えてください
      明日の大阪の天気
      明日の大阪の天気です
      明日の大阪の天気を教えてください
      大阪の明日の天気
      大阪の明日の天気です
      大阪の明日の天気を教えてください
      
      da=initialize
      もう一度はじめから
      はじめから
      はじめからお願いします
      最初から
      最初からお願いします
      初期化してください
      キャンセル
      すべてキャンセル
      
      da=correct-info
      大阪じゃない
      大阪じゃなくて
      大阪じゃないです
      大阪ではありません
      明日じゃない
      明日じゃなくて
      明日じゃないです
      明日ではありません
      天気じゃない
      天気じゃなくて
      天気じゃないです
      天気ではありません
      

    次に上記代表データを読込んで対象のタグに応じた辞書(都道府県名、日付、情報種別)からランダムで一つ抽出し、学習データを作成するプログラム(generate_da_samples.py)を準備する。
    ※ 下記のプログラムでは、代表データ一行あたり\(1000\)個の学習データを作成している。

    • generate_da_samples.py
      import re
      import random
      import json
      import xml.etree.ElementTree
      
      # 都道府県名のリスト
      prefs = ['三重', '京都', '佐賀', '兵庫', '北海道', '千葉', '和歌山', '埼玉', '大分',
              '大阪', '奈良', '宮城', '宮崎', '富山', '山口', '山形', '山梨', '岐阜', '岡山',
              '岩手', '島根', '広島', '徳島', '愛媛', '愛知', '新潟', '東京',
              '栃木', '沖縄', '滋賀', '熊本', '石川', '神奈川', '福井', '福岡', '福島', '秋田',
              '群馬', '茨城', '長崎', '長野', '青森', '静岡', '香川', '高知', '鳥取', '鹿児島']
      
      # 日付のリスト
      dates = ["今日","明日"]
      
      # 情報種別のリスト
      types = ["天気","気温"]
      
      # サンプル文に含まれる単語を置き換えることで学習用事例を作成
      def random_generate(root):
          buf = ""
          # タグがない文章の場合は置き換えしないでそのまま返す
          if len(root) == 0:
              return root.text
          # タグで囲まれた箇所を同じ種類の単語で置き換える
          for elem in root:
              if elem.tag == "place":
                  pref = random.choice(prefs)
                  buf += pref
              elif elem.tag == "date":
                  date = random.choice(dates)
                  buf += date
              elif elem.tag == "type":
                  _type =  random.choice(types)
                  buf += _type
              if elem.tail is not None:
                  buf += elem.tail
          return buf
      
      # 学習用ファイルの書き出し先
      fp = open("da_samples.dat","w")
      
      da = ''
      # examples.txt ファイルの読み込み
      for line in open("examples.txt","r"):
          line = line.rstrip()
          # da= から始まる行から対話行為タイプを取得
          if re.search(r'^da=',line):
              da = line.replace('da=','')
          # 空行は無視
          elif line == "":
              pass
          else:
              # タグの部分を取得するため,周囲にダミーのタグをつけて解析
              root = xml.etree.ElementTree.fromstring("<dummy>"+line+"</dummy>")
              # 各サンプル文を1000倍に増やす
              for i in range(1000):
                  sample = random_generate(root)
                  # 対話行為タイプ,発話文,タグとその文字位置を学習用ファイルに書き出す
                  fp.write(da + "\t" + sample + "\n")
      
      fp.close()
      

    以下、上記generate_da_samples.py処理の実行順解説。

    • 都道府県名、日付、情報種別の辞書を定義

      … (省略) …
      # 都道府県名のリスト
      prefs = ['三重', '京都', '佐賀', '兵庫', '北海道', '千葉', '和歌山', '埼玉', '大分',
              '大阪', '奈良', '宮城', '宮崎', '富山', '山口', '山形', '山梨', '岐阜', '岡山',
              '岩手', '島根', '広島', '徳島', '愛媛', '愛知', '新潟', '東京',
              '栃木', '沖縄', '滋賀', '熊本', '石川', '神奈川', '福井', '福岡', '福島', '秋田',
              '群馬', '茨城', '長崎', '長野', '青森', '静岡', '香川', '高知', '鳥取', '鹿児島']
      
      # 日付のリスト
      dates = ["今日","明日"]
      
      # 情報種別のリスト
      types = ["天気","気温"]
      … (省略) …
      
    • da_samples.datを書き込みモードで定義
      作成する学習用データファイルda_samples.datを新規作成し、書き込みモードで開いたfpを定義。
      ※ openは、指定したファイルが存在しなければ新規作成した状態で開き、存在していれば上書き状態で開く。
      但し、指定したファイルがおかれているディレクトリが存在しない場合は、エラーとなるので注意。

      … (省略) …
      # 学習用ファイルの書き出し先
      fp = open("da_samples.dat","w")
      … (省略) …
      
    • examples.txtファイルの読み込み
      上記で準備した代表データ(examples.txt)を読み込みモードで開き、読み込んだ行数分ループし(*3)or(*4)or(*5)を実行。

      • (*3)正規表現モジュールreda=から始まるかどうかチェックし、始まるのであれば、右辺の発話行為タイプをdaに保持。
      • (*4)空白行とみなしスキップし、何もしない。
      • (*5)XMLとして解析するため(最上位のタグは1つでなければならない)、対象行をdummyタグで囲い、fromstringで文字列からxml.etree.ElementTree.Elementにパースしたものをrootに保持。 このrootを引数に\(1000\)回random_generateメソッド(処理詳細は下記(*6)を参照)を呼出し、対話行為タイプとrandom_generateの戻り値samplefpに書き込む。
        da = ''
        for line in open("examples.txt","r"):
            line = line.rstrip()
            if re.search(r'^da=',line):
                da = line.replace('da=','')
            elif line == "":
                pass
            else:
                root = xml.etree.ElementTree.fromstring("<dummy>"+line+"</dummy>")
                for i in range(1000):
                    sample = random_generate(root)
                    fp.write(da + "\t" + sample + "\n")
        
      • (*6)random_generate メソッドの処理 引数のroot内にタグが存在しない場合(len(root) == 0)、root直下のテキストをそのまま返す。
        以降は、root内のタグに応じて、都道府県名 or 日付 or 情報種別の辞書からそれぞれからランダム抽出した結果と対象タグ末尾の発話文(tail)をbufに加算して返す。
        def random_generate(root):
            buf = ""
            if len(root) == 0:
                return root.text
            for elem in root:
                if elem.tag == "place":
                    pref = random.choice(prefs)
                    buf += pref
                elif elem.tag == "date":
                    date = random.choice(dates)
                    buf += date
                elif elem.tag == "type":
                    _type =  random.choice(types)
                    buf += _type
                if elem.tail is not None:
                    buf += elem.tail
            return buf
        
    • 最後にfpを閉じて処理終了

      … (省略) …
      fp.close()
      … (省略) …
      

    最後に実行確認

    • generate_da_samples.pyの実行確認
      上記の代表データ(examples.txt)がgenerate_da_samples.pyと同じフォルダ内にある状態で実行。
      (もちろんgenerate_da_samples.pyまでのファイルパスは各自環境に合わせて読み替える。)

      $ python ~/gitlocalrep/dsbook/generate_da_samples.py
      
    • da_samples.datの確認
      代表データ(examples.txt)が40個あり、それぞれから\(1000\)個を生成するため、重複を含めた出力行数は \(40 \times 1000 = 40000\) 行となる。

      ※ 同じテンプレートから置換して作った文は互いに強く似る。モデル評価では、生成後の行を無作為に分けるだけでなく、元テンプレート単位で学習用と評価用を分離し、類似文の混入によるデータ漏洩を避ける。

      $ cat ~/gitlocalrep/dsbook/da_samples.dat
      request-weather 高知
      request-weather 大分
      request-weather 京都
      … (省略) …
      equest-weather 青森です
      request-weather 神奈川です
      request-weather 神奈川です
      … (省略) …
      request-weather 今日
      request-weather 明日
      request-weather 今日
      … (省略) …
      initialize もう一度はじめから
      initialize はじめから
      initialize はじめからお願いします
      … (省略) …
      correct-info 福岡じゃない
      correct-info 神奈川じゃない
      correct-info 広島じゃない
      … (省略) …
      

    以上で学習データの準備が完了。

まとめ

  • フレームベースでは、発話から対話行為タイプやスロット情報を推定。
  • SVMは発話行為タイプの分類に使う。
  • 学習データの設計と品質が、後続の推定精度に大きく影響する。

参考文献

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



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