Chatterbot+Cubismで会話をしてみる

今回はVue.jsとFlaskで文章の受け渡しを行って、Chatterbotの返答に合わせてCubismWeb(Live2D Cubism3SDK for Web)のモデルを動かしてみます。

Chatterbotとは

機械学習ベースの簡易的な対話エンジンを作ることができるPythonライブラリ
会話のデータ(コーパス)を用意することで、入力された文章に最も近いデータ(文字列)を検索して応答を生成する

全体の流れ

1. Vueのinput要素にテキストを入力する
2. 入力されたテキストをChatterbotに渡す
3. Chatterbotが生成した返答をVueに返す
4. 返された文章に合わせてCubismWebのモデルを更新する

使用するイラスト

CubismWebで表示するモデルです。

使用するイラスト
© Unity Technologies Japan/UCL

上記モデルには以前の記事で Live2D のテンプレート「FaceRig」を適用しています。
テンプレートを適用する手順についてはこちらで紹介しています。

Live2Dのテンプレート機能[FaceRig]
この記事では、FaceRig向けのLive2Dモデルの構造(デフォーマ)やパラメータを、制作するモデルに移す機能(テンプレート機能)について紹介しています。

動作確認用のサンプル

ChromeとEdgeで動作を確認しています。
サンプルでは予めchatterbot_corpusに用意した文章で会話を行っています。

対話エンジンのボディとしてのCubismWeb

対話エンジン(Chatterbot)のボディとしてCubismWebのモデルを適用するまでの流れ

1. コーパスを編集する

まずは会話のトレーニングデータとなるchatterbot_corpusをgithubからダウンロードします。
現在(2019/4/20)の時点では日本語に翻訳されたデータも入っていたので、ある程度はそのまま使えます。
corpus_dataはymlファイルとしてカテゴリごとに用意されているので、簡単に内容を編集できます。

corpus

2. chatterbotを動かしてみる

最初にchatterbotをインストールします。
condaでは見つからなかったので、Anaconda環境では避けたほうが良いかもしれないです。

pip install chatterbot

日本語のトレーニングコーパスを指定して「おはよう、元気?」と問いかけてみます。

sample.py
from chatterbot import ChatBot
from chatterbot.trainers import ChatterBotCorpusTrainer

chatbot = ChatBot("automaton")
trainer = ChatterBotCorpusTrainer(chatbot)
# トレーニング用のコーパスを指定
trainer.train("chatterbot.corpus.japanese.conversations")
# 問いかけ
response = chatbot.get_response("おはよう、元気?")
print(response)

ファイルを実行すると「おはよう、元気?」に対応する言葉が返ってきます。

動作確認

3. VueでUIを作成する

Vue.jsでテキストを入力する場所や、返答を表示する場所を作成します。

chatCubism.html
<div id="message_window">
  <p id="ans">[[ answer ]]</p>
</div>
<div id="input">
  <div class="inputA">
    <input v-model="message">
    <button @click="getAnswer" id="btn">try</button>
  </div>
  <div class="message_container">
    <p id="mes">[[ message ]]</p>
  </div>
</div>

UI

4. テキストの受け渡しを処理する

Vueのinput要素に入力されたテキストをchatterbotに渡して、返事を受け取ります。

chatCubism.html
<script>
const talk = new Vue({
  el: "#exa",
  delimiters: ["[[", "]]"],
  data: {
    message: '',
    answer: '//メッセージを入力してください//'
  },
  methods: {
    update_answer: function(str) {
      this.answer = str;
    },
    getAnswer:function() {
      const src = this.message;
      callback = this.update_answer;
      fetch('http://localhost:5000/chatCubism', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(src),
      }).then(function(res){
         return res.json();
      }).then(function(src) {
         callback(src.ans);     //chatterbotからの返事
         const count = 0;
         const countup = function() {
           callback('');
         }
         setTimeout(countup, 7000);
      }).catch(function(error) {
         console.log(error)
      })
   }}
})
</script>

UIの確認

5. chatterbotの処理

2.で行った動作確認と同じ流れでVueから渡されたテキストを使用します。
コーパスを(“chatterbot.corpus.japanese”)とすると、日本語の全てのカテゴリを指定できます。

web.py
def answer(src):
    chatbot = ChatBot("automaton")
    trainer = ChatterBotCorpusTrainer(chatbot)
    trainer.train("chatterbot.corpus.japanese.botprofile")
    response = chatbot.get_response(src)
    return response

chatterbotが生成した返答をVueに返します。

web.py
@app.route('/chatCubism', methods = ['GET', 'POST'])
def chatCubism():
    src = request.json
    if request.method == 'POST':
        responce = answer(src)
        ans = str(responce)
        return jsonify({'ans': ans})
    elif request.method == 'GET':
        return render_template('chatCubism.html')

返事を生成

6. CubismWebの画面を表示

以前の記事(アヤメの分類)と同じ手順でCubismWebの画面を設置します。

Vue+Flask+CubismWebでアヤメの分類を行う
機械学習のライブラリ「scikit-learn」を使用したアヤメの分類結果を Cubism SDK for Web に渡して、Live2Dのモデルを動かすことを試みています。

画面を表示

7. 会話の内容に合わせてCubismWebのモデルを更新

今回は入力したテキストに「?」が含まれているか、返答する文章が長いか短いかを参照して、再生するモーションを切り替えることにしました。

用意したモーションは下記の4種類です。
モーションA:入力したテキストに「?」が含まれている、返事の長さが11文字以上
モーションB:入力したテキストに「?」が含まれている、返事の長さが10文字以下
モーションC:入力したテキストに「?」が含まれていない、返事の長さが11文字以上
モーションD:入力したテキストに「?」が含まれていない、返事の長さが10文字以下

lapplive2dmanager.ts
window.onload = () => {
  let motionNum = 0;
  const getbtn = document.getElementById("btn");
  getbtn.onclick = () => {
  setTimeout( () => {
    const getmes = document.getElementById("mes");
    const getans = document.getElementById("ans");
    if (getmes.textContent.indexOf('初回読み込み') != -1) {
      motionNum = 0;
    } else if (getmes.textContent.indexOf('?') != -1) {
      if (getans.textContent.length > 10) {
        motionNum = 1;
      } else if (getans.textContent.length <= 10) {
        motionNum = 2;
      }
    } else {
      if (getans.textContent.length > 10) {
        motionNum = 3;
      } else if (getans.textContent.length <= 10) {
        motionNum = 4;
      }
    }
    this.onUpdate = () => {

    //onUpdateの中身と同じ//

    if (motionNum == 0) {      //待機時のモーションを再生
        return;
    }
    for (let i = 0; i < this._models.getSize(); i++) {
    if (motionNum == 1) {
        this._models.at(i).startMotion(LAppDefine.MotionGroupAdd, 1, LAppDefine.PriorityNormal);
    } else if (motionNum == 2) {
        this._models.at(i).startMotion(LAppDefine.MotionGroupAdd, 2, LAppDefine.PriorityNormal);
    } else if (motionNum == 3) {
        this._models.at(i).startMotion(LAppDefine.MotionGroupAdd, 3, LAppDefine.PriorityNormal);
    } else if (motionNum == 4) {
        this._models.at(i).startMotion(LAppDefine.MotionGroupAdd, 4, LAppDefine.PriorityNormal);
      }
    }
    motionNum = 0;    //一回モーションを再生したら、待機時のモーションに移行する
    }
  },2000);    //メッセージが表示されるまで少し時間がかかるので、その分処理を遅らせる
  }
}

モデル更新

キャラクタが表示されるだけで印象がだいぶ変わりますね。

タイトルとURLをコピーしました