【2022年版】 Rasa 中級編

この記事は、 [【2022年版】 Rasa 入門](https://qiita.com/Zect/items/e341e43fa9cf98529942) の続きです。

# 初めに
どうも、お待たせしました！
前回、書ききれなかった"改造編"行ってみましょう！

---
さて、何を作るか,,,

少し悩みましたが、みんな大好き、あのシーンを作っていきます。
といっても、一方的な会話を対話式に変換するので、原作とは異なる会話が繰り広げられますが、気にしないでください（）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/974385/82d56e5c-4472-4a0a-c584-5f6433ba2f37.png)

# 0. 会話構成
さて、まずは、会話構成を考えていきましょう。

野球に誘う時に必要な情報は何でしょう。

そう、「時間」と「場所」です！

なので、「時間」と「場所」を聞きます。

- **自分：「よう」** `(挨拶)`
- BOT：「おい！野球しようぜ！」
- BOT：「何時から来れるか？」
- **自分：「16時くらいから行けそう！」** `(時間を言う)`
- BOT：「オッケー！場所はどこがいいと思う？」
- **自分：「やっぱ、空き地でしょ！」** `(場所を言う)`
- BOT：「おーけー！じゃぁ、空き地で16時くらいに待ってるからな！」 `(時間と場所の確認)`
- BOT：「絶対に来いよ！」

こんな感じでしょうか。

では、作っていきましょう！

# 1. BOTのメッセージ
まずは、簡単なところから,,,

やはり、BOTの返信部分でしょうか。
Actionの作成等は、後でやりたいので、`1`としておきました。

```domain.yml
...

responses:
  utter_greet:
    - text: "おい！野球しようぜ！"
  utter_ask_time:
    - text: "何時から来れるか？"
  utter_ask_place:
    - text: "オッケー！場所はどこがいいと思う？"
  utter_force:
    - text: "絶対に来いよ！"
  utter_default:
    - text: "何言ってんだ、お前？"

...
```

こんな感じですかね。

こういうタイプの文章、書きなれていないんで、少しおかしいところがあるかもしれませんが、概ね、合っていると思います。

# 2. スロット
会話構成をみると、「時間」と「場所」を取得するプロセスがあります。
なので、時間と場所を格納する用のスロットを用意します。

スロットは、"`Entity`"と呼ばれる、単語のカテゴリーを指定してあげなければなりません。
今回は、日本語の処理をしているので、この種類分けは`GiNZA`のものを使うと、スムーズに進みます。
[一覧はここ](https://github.com/megagonlabs/ginza/blob/develop/ginza/ene_ontonotes_mapper.py)

https://rasa.com/docs/rasa/domain/#slot-types

https://rasa.com/docs/rasa/domain#entities

https://github.com/megagonlabs/ginza/blob/develop/ginza/ene_ontonotes_mapper.py

```domain.yml
...

entities:
  - Time
  - City

slots:
  play_time:
    type: text
    mappings:
    - type: from_entity
      entity: Time
  play_place:
    type: text
    mappings:
    - type: from_entity
      entity: City

...
```

# 3. フォーム

```domain.yml
...

forms:
  play_time_form:
    required_slots:
      - play_time
  play_place_form:
    required_slots:
      - play_place

...
```

今回は、時間と場所を指定するので、`play_time`と`play_place`の二つを受け取るようにします。

[公式](https://rasa.com/docs/rasa/forms) にも書いてありますが、二つの変数を受け取るフォームも作れますが、今回は、「一つ受け取って、返答して」を二回するので、二つフォームを作成します。

# 4. インテンツ
おっと、もう少しで忘れるところでした

受け取る文章をrule/storyに回すために、
挨拶・時間・場所
のラベルを用意します。

```domain.yml
...

intents:
  - greet
  - nlu_fallback

...
```

こんな感じです

説明はいりませんね。

# 5. 学習データ
さて、次は学習データの作成に行きましょう。

```data/nlu.yml
version: "3.0"

nlu:

- intent: greet
  examples: |
    - こんにちは
    - おはようございます
    - こんばんは
    - やっほー
    - よう

```

時間と場所に関しての発言は、Ruleで取得しちゃうので、ここでの定義の必要はありません。

# 6. ストーリー

```data/stories.yml
version: "3.0"

stories:

- story: Greeting and Ask Time
  steps:
  - intent: greet
  - action: utter_greet
  - action: utter_ask_time
  - action: play_time_form
  - active_loop: play_time_form
```

これだけです。
挨拶で時間を聞くプロセスを起動する感じです。

これ以外の発言は、`nlu.yml`で学習させていないので、ruleで取っちゃいます。

# 7. ルール

```data/rules.yml
version: "3.0"

rules:

- rule: Fallback
  steps:
  - intent: nlu_fallback
  - action: utter_default

- rule: Get Time and Ask Place
  condition:
  - active_loop: play_time_form
  steps:
  - action: play_time_form
  - active_loop: null
  - slot_was_set:
    - requested_slot: null
  - action: utter_ask_place
  - action: play_place_form
  - active_loop: play_place_form

- rule: GET Place and Check
  condition:
  - active_loop: play_place_form
  steps:
  - action: play_place_form
  - active_loop: null
  - slot_was_set:
    - requested_slot: null
  - action: action_check_PandT
  - action: utter_force
  - action: action_restart
```

一番上がFallback（例外処理）です。

二つ目は、時間を聞くためのフォームが起動している時に、発言から時間を取り出して、場所のフォームを起動しています。

三つめは、場所を聞くフォームが起動している時に、発言から場所を取り出して、時間・場所の確認と会話データのリセットを行っています。

# 8. アクション①
```actions/actions.py
from rasa_sdk import Action

class ActionCheckPlaceTime(Action):

    def name(self):
        return "action_check_PandT"

    def run(self, dispatcher, tracker, domain):
        time = tracker.get_slot("play_time")
        place = tracker.get_slot("play_place")
        dispatcher.utter_message(text=f"おーけー！じゃぁ、{place}で{time}くらいに待ってるからな！")

        return []
```

サンプルで作ったものを、時間と場所を聞くように、少し変えただけです。

# 9. アクション②
```domain.yml
...

actions:
  - action_check_PandT

...
```

ここで記述するアクション名は、上の`actions.py`の`ActionCheckPlaceTime`の`name()`で記述した、`action_check_PandT`にします。
ここを間違えると、最後の確認の部分(アクション)がうまく動かないので、気を付けてください。

# 10. 実験

:::note warn
えー、
ここから、僕の理解不足のせいで起こった不具合に悩まされることになります。
お急ぎの方は、どうぞ、13番まで飛ばしてください。
:::

さて、準備が整いました。
学習させて、試してみましょう。

```console:console
> rasa train
```

あ、actionの起動も忘れずに

```console:console
> rasa run actions
```

```console:console
> rasa shell
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/974385/d0467618-f48b-6800-0ed8-0718514438a9.png)
おや？
うまくいきませんね。

なので、`config.yml`の`FallbackClassifier`の閾値を下げます。

```diff_yaml:config.yml
...
pipeline:
...
  - name: FallbackClassifier
-   threshold: 1
+   threshold: 0.7
...
```

もう一度、上のコマンドを実行してみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/974385/3471e3d9-6926-688f-b524-311b1cc984d5.png)
閾値が問題ではなさそうです。

```diff_yaml:config.yml
...
pipeline:
...
  - name: FallbackClassifier
-   threshold: 0.7
+   threshold: 1
...
```

とりあえず、戻しましょう。

# 11. Fallback処理を全部消してみる。

```diff_yaml:domain.yml
...

intents:
  - greet
  - thankyou
-  - nlu_fallback

...
```

```diff_yaml:config.yml
...

pipeline:
...
-  - name: FallbackClassifier
-    threshold: 0.8

...
```

```diff_yaml:data/rules.yml
...

rules:

- - rule: Fallback
-   steps:
-   - intent: nlu_fallback
-   - action: utter_default

...
```

全部消しました。

試してみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/974385/3471e3d9-6926-688f-b524-311b1cc984d5.png)

だめですね,,,

---

二日後

---

『もしかして、環境が汚れてる説、あるくない？』

https://rasa.com/docs/rasa/installation/#2-virtual-environment-setup

ここに、「仮想環境を使うことを強く推奨する」という記述があります。
これって、もしかして、作れば作るほど、環境が汚れていって、最終的にPCがうまく動かなくなる可能性を暗示しているのでは
と考えました。

そこで、仮想環境を新規作成することにしました。

# 12. 仮想環境の再構築

```:console
> deactivate
> rmdir venv
> python -m venv ./venv
> .\venv\Scripts\activate
```

完了☆

では、必要なライブラリたちをインストールしていきましょう。

```console:console
> pip install -U pip setuptools
> pip install rasa
> pip install spacy
> pip install ginza ja-ginza
```

これでOK

# 13. 実行

```console:console
> rasa train
```

すると、、、

```consoel:console
c:\users\zect\venv\lib\site-packages\rasa\shared\utils\io.py:99: UserWarning: Training data file C:\Users\Zect\NLP\6-g\domain.yml has a lower format version than your Rasa Open Source installation: 3.0 < 3.1. Rasa Open Source will read the file as a version 3.1 file. Please update your version key to 3.1. See https://rasa.com/docs/rasa/training-data-format.
```

こんなwarnが出てきました。

バージョンを"`3.0`"から"`3.1`"に変えた方がいいぞ
ってことらしい

なんで、変更していきます。

変更する場所は、
- domain.yml
- data/
  - nlu.yml
  - rules.yml
  - stories/yml

この四つです。

さて、修正も終わりましたし、再度実行していきます！

```console:console
> rasa train
> rasa shell
```

おっと、忘れるところでした。
Actionの実行もしましょう

```console:console
> rasa run actions
```

では、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/974385/ebffa3a7-aa56-739f-daa8-4aadcb5068e3.png)
ん？

やっぱり、おかしいですね、、、

# 14. intentsは二つ以上用意しましょう
はい。
結論から言います。
intentsは二つ以上用意しないとうまく動作しません。

てことで、定義するだけ定義して、使わない、"ThankYou"を追加していきましょう。

```diff_yaml:domain.yml
...

intents:
  - greet
+  - thankyou
  - nlu_fallback

...
```

```data/nlu.yml
...

- intent: thankyou
  examples: |
    - ありがとう
    - ありがとな
    - ありがとね
    - ありがとうございます
    - ありがとうございました
    - サンキュー

...
```

では、

```console:console
> rasa train
> rasa shell
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/974385/6784df9f-2698-31a6-610c-91e6bb168fa2.png)
うおおおおおおおおおお!!!!!!!

うまく行きました！！！
いい感じにFallbackしてますね（笑）

# 終わりに
さて、どうだったでしょうか？
僕は超楽しかったです。

ただ、rule/storyが理解できていないところがあるので、もっときれいに書ける気がします。
これからもRasa/AI関連に関わっていくつもりなので、何か進展があれば、記事にして共有しようと思っています。

お疲れ様です！
じゃ ﾉｼ