46 KiB
Systemy Dialogowe
9. Zarządzanie dialogiem z wykorzystaniem reguł [laboratoria]
Marek Kubis (2021)
Zarządzanie dialogiem z wykorzystaniem reguł
Agent dialogowy wykorzystuje do zarządzanie dialogiem dwa moduły:
monitor stanu dialogu (dialogue state tracker, DST) — moduł odpowiedzialny za śledzenie stanu dialogu.
taktykę prowadzenia dialogu (dialogue policy) — moduł, który na podstawie stanu dialogu podejmuje decyzję o tym jaką akcję (akt systemu) agent ma podjąć w kolejnej turze.
Oba moduły mogą być realizowane zarówno z wykorzystaniem reguł jak i uczenia maszynowego. Mogą one zostać również połączone w pojedynczy moduł zwany wówczas _menedżerem dialogu.
Przykład
Zaimplementujemy regułowe moduły monitora stanu dialogu oraz taktyki dialogowej a następnie osadzimy je w środowisku _ConvLab-2, które służy do ewaluacji systemów dialogowych.
Uwaga: Niektóre moduły środowiska _ConvLab-2 nie są zgodne z najnowszymi wersjami Pythona,
dlatego przed uruchomieniem poniższych przykładów należy się upewnić, że mają Państwo interpreter
Pythona w wersji 3.7. W przypadku nowszych wersji Ubuntu Pythona 3.7 można zainstalować z
repozytorium deadsnakes
, wykonując polecenia przedstawione poniżej.
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt update
sudo apt install python3.7 python3.7-dev python3.7-venv
W przypadku innych systemów można skorzystać np. z narzędzia pyenv lub środowiska conda.
Ze względu na to, że _ConvLab-2 ma wiele zależności zachęcam również do skorzystania ze środowiska
wirtualnego venv
, w którym moduły zależne mogą zostać zainstalowane.
W tym celu należy wykonać następujące polecenia
python3.7 -m venv convenv # utworzenie nowego środowiska o nazwie convenv
source convenv/bin/activate # aktywacja środowiska w bieżącej powłoce
pip install --ignore-installed jupyter # instalacja jupytera w środowisku convenv
Po skonfigurowaniu środowiska można przystąpić do instalacji _ConvLab-2, korzystając z następujących poleceń
mkdir -p l08
cd l08
git clone https://github.com/thu-coai/ConvLab-2.git
cd ConvLab-2
pip install -e .
python -m spacy download en_core_web_sm
cd ../..
Po zakończeniu instalacji należy ponownie uruchomić notatnik w powłoce, w której aktywne jest środowisko wirtualne _convenv.
jupyter notebook 08-zarzadzanie-dialogiem-reguly.ipynb
Działanie zaimplementowanych modułów zilustrujemy, korzystając ze zbioru danych MultiWOZ (Budzianowski i in., 2018), który zawiera wypowiedzi dotyczące m.in. rezerwacji pokoi hotelowych, zamawiania biletów kolejowych oraz rezerwacji stolików w restauracji.
Monitor Stanu Dialogu
Do reprezentowania stanu dialogu użyjemy struktury danych wykorzystywanej w _ConvLab-2.
# from convlab2.util.multiwoz.state import default_state
from utils.state import default_state
default_state()
{'user_action': [], 'system_action': [], 'belief_state': {'cinema': {'book': {'title': '', 'date': '', 'time': '', 'quantity': '', 'seats': '', 'area': '', 'interval': ''}, 'semi': {'goal': ''}}}, 'request_state': {}, 'terminated': False, 'history': []}
Metoda update
naszego monitora stanu dialogu będzie przyjmować akty użytkownika i odpowiednio
modyfikować stan dialogu.
W przypadku aktów typu inform
wartości slotów zostaną zapamiętane w słownikach odpowiadających
poszczególnym dziedzinom pod kluczem belief_state
.
W przypadku aktów typu request
sloty, o które pyta użytkownik zostaną zapisane pod kluczem
request_state
.
import json
import os
from convlab2.dst.dst import DST
from convlab2.dst.rule.multiwoz.dst_util import normalize_value
from convlab2.util.multiwoz.multiwoz_slot_trans import REF_SYS_DA
class SimpleRuleDST(DST):
def __init__(self):
DST.__init__(self)
self.state = default_state()
self.value_dict = json.load(open('utils/value_dict.json'))
def update(self, user_act=None):
for intent, domain, slot, value in user_act:
domain = domain.lower()
intent = intent.lower()
value = value.lower()
# if domain in ['unk', 'cinema']:
# continue
k = slot
if intent == 'inform':
# k = REF_SYS_DA[domain.capitalize()].get(slot, slot)
# if k is None:
# continue
domain_dic = self.state['belief_state'][domain]
if k in domain_dic['semi']:
# nvalue = normalize_value(self.value_dict, domain, k, value)
self.state['belief_state'][domain]['semi'][k] = value
elif k in domain_dic['book']:
self.state['belief_state'][domain]['book'][k] = value
elif k.lower() in domain_dic['book']:
self.state['belief_state'][domain]['book'][k.lower()] = value
elif intent == 'request':
# k = REF_SYS_DA[domain.capitalize()].get(slot, slot)
if domain not in self.state['request_state']:
self.state['request_state'][domain] = {}
if k not in self.state['request_state'][domain]:
self.state['request_state'][domain][k] = 0
return self.state
def init_session(self):
self.state = default_state()
'''
class SimpleRuleDST(DST):
def __init__(self):
DST.__init__(self)
self.state = default_state()
self.value_dict = json.load(open('l08/ConvLab-2/data/multiwoz/value_dict.json'))
def update(self, user_act=None):
for intent, domain, slot, value in user_act:
domain = domain.lower()
intent = intent.lower()
if domain in ['unk', 'general', 'booking']:
continue
if intent == 'inform':
k = REF_SYS_DA[domain.capitalize()].get(slot, slot)
if k is None:
continue
domain_dic = self.state['belief_state'][domain]
if k in domain_dic['semi']:
nvalue = normalize_value(self.value_dict, domain, k, value)
self.state['belief_state'][domain]['semi'][k] = nvalue
elif k in domain_dic['book']:
self.state['belief_state'][domain]['book'][k] = value
elif k.lower() in domain_dic['book']:
self.state['belief_state'][domain]['book'][k.lower()] = value
elif intent == 'request':
k = REF_SYS_DA[domain.capitalize()].get(slot, slot)
if domain not in self.state['request_state']:
self.state['request_state'][domain] = {}
if k not in self.state['request_state'][domain]:
self.state['request_state'][domain][k] = 0
return self.state
def init_session(self):
self.state = default_state()
'''
"\nclass SimpleRuleDST(DST):\n def __init__(self):\n DST.__init__(self)\n self.state = default_state()\n self.value_dict = json.load(open('l08/ConvLab-2/data/multiwoz/value_dict.json'))\n\n def update(self, user_act=None):\n for intent, domain, slot, value in user_act:\n domain = domain.lower()\n intent = intent.lower()\n\n if domain in ['unk', 'general', 'booking']:\n continue\n\n if intent == 'inform':\n k = REF_SYS_DA[domain.capitalize()].get(slot, slot)\n\n if k is None:\n continue\n\n domain_dic = self.state['belief_state'][domain]\n\n if k in domain_dic['semi']:\n nvalue = normalize_value(self.value_dict, domain, k, value)\n self.state['belief_state'][domain]['semi'][k] = nvalue\n elif k in domain_dic['book']:\n self.state['belief_state'][domain]['book'][k] = value\n elif k.lower() in domain_dic['book']:\n self.state['belief_state'][domain]['book'][k.lower()] = value\n elif intent == 'request':\n k = REF_SYS_DA[domain.capitalize()].get(slot, slot)\n\n if domain not in self.state['request_state']:\n self.state['request_state'][domain] = {}\n if k not in self.state['request_state'][domain]:\n self.state['request_state'][domain][k] = 0\n\n return self.state\n\n def init_session(self):\n self.state = default_state()\n"
W definicji metody update
zakładamy, że akty dialogowe przekazywane do monitora stanu dialogu z
modułu NLU są czteroelementowymi listami złożonymi z:
- nazwy aktu użytkownika,
- nazwy dziedziny, której dotyczy wypowiedź,
- nazwy slotu,
- wartości slotu.
Zobaczmy na kilku prostych przykładach jak stan dialogu zmienia się pod wpływem przekazanych aktów użytkownika.
dst = SimpleRuleDST()
dst.state
{'user_action': [], 'system_action': [], 'belief_state': {'cinema': {'book': {'title': '', 'date': '', 'time': '', 'quantity': '', 'seats': '', 'area': '', 'interval': ''}, 'semi': {'goal': ''}}}, 'request_state': {}, 'terminated': False, 'history': []}
dst.update([['Inform', 'Cinema', 'time', '15:00'], ['Inform', 'Cinema', 'title', 'Batman']])
dst.state['belief_state']['cinema']
{'book': {'title': 'batman', 'date': '', 'time': '15:00', 'quantity': '', 'seats': '', 'area': '', 'interval': ''}, 'semi': {'goal': ''}}
dst.update([['Inform', 'Cinema', 'area', 'na górze na środku']])
dst.state['belief_state']['cinema']
{'book': {'title': 'batman', 'date': '', 'time': '15:00', 'quantity': '', 'seats': '', 'area': 'na górze na środku', 'interval': ''}, 'semi': {'goal': ''}}
dst.update([['Request', 'Cinema', 'date', '?']])
dst.state['request_state']
{'cinema': {'date': 0}}
dst.update([['Inform', 'Hotel', 'Day', 'tuesday'], ['Inform', 'Hotel', 'People', '2'], ['Inform', 'Hotel', 'Stay', '4']])
dst.state['belief_state']['hotel']
[1;31mRunning cells with 'Python 3.6.15 ('sysdial')' requires ipykernel package.
Run the following command to install 'ipykernel' into the Python environment.
Command: 'conda install -n sysdial ipykernel --update-deps --force-reinstall'
[1;31mRunning cells with 'Python 3.6.15 ('sysdial')' requires ipykernel package.
Run the following command to install 'ipykernel' into the Python environment.
Command: 'conda install -n sysdial ipykernel --update-deps --force-reinstall'
dst.state
{'user_action': [], 'system_action': [], 'belief_state': {'cinema': {'book': {'title': 'batman', 'date': '', 'time': '15:00', 'quantity': '', 'seats': '', 'area': 'na górze na środku', 'interval': ''}, 'semi': {'goal': ''}}}, 'request_state': {'cinema': {'date': 0}}, 'terminated': False, 'history': []}
Taktyka Prowadzenia Dialogu
Prosta taktyka prowadzenia dialogu dla systemu rezerwacji pokoi hotelowych może składać się z następujących reguł:
Jeżeli użytkownik przekazał w ostatniej turze akt typu
Request
, to udziel odpowiedzi na jego pytanie.Jeżeli użytkownik przekazał w ostatniej turze akt typu
Inform
, to zaproponuj mu hotel spełniający zdefiniowane przez niego kryteria.Jeżeli użytkownik przekazał w ostatniej turze akt typu
Inform
zawierający szczegóły rezerwacji, to zarezerwuj pokój.
Metoda predict
taktyki SimpleRulePolicy
realizuje reguły przedstawione powyżej.
from collections import defaultdict
import copy
import json
from copy import deepcopy
from convlab2.policy.policy import Policy
from convlab2.util.multiwoz.dbquery import Database
from convlab2.util.multiwoz.multiwoz_slot_trans import REF_SYS_DA, REF_USR_DA
class SimpleRulePolicy(Policy):
def __init__(self):
Policy.__init__(self)
self.db = Database()
def predict(self, state):
self.results = []
system_action = defaultdict(list)
user_action = defaultdict(list)
for intent, domain, slot, value in state['user_action']:
user_action[(domain, intent)].append((slot, value))
for user_act in user_action:
self.update_system_action(user_act, user_action, state, system_action)
# Reguła 3
if any(True for slots in user_action.values() for (slot, _) in slots if slot in ['Stay', 'Day', 'People']):
if self.results:
system_action = {('Booking', 'Book'): [["Ref", self.results[0].get('Ref', 'N/A')]]}
system_acts = [[intent, domain, slot, value] for (domain, intent), slots in system_action.items() for slot, value in slots]
state['system_action'] = system_acts
return system_acts
def update_system_action(self, user_act, user_action, state, system_action):
domain, intent = user_act
constraints = [(slot, value) for slot, value in state['belief_state'][domain.lower()]['semi'].items() if value != '']
self.results = deepcopy(self.db.query(domain.lower(), constraints))
# Reguła 1
if intent == 'Request':
if len(self.results) == 0:
system_action[(domain, 'NoOffer')] = []
else:
for slot in user_action[user_act]:
kb_slot_name = REF_SYS_DA[domain].get(slot[0], slot[0])
if kb_slot_name in self.results[0]:
system_action[(domain, 'Inform')].append([slot[0], self.results[0].get(kb_slot_name, 'unknown')])
# Reguła 2
elif intent == 'Inform':
if len(self.results) == 0:
system_action[(domain, 'NoOffer')] = []
else:
system_action[(domain, 'Inform')].append(['Choice', str(len(self.results))])
choice = self.results[0]
if domain in ["Hotel", "Attraction", "Police", "Restaurant"]:
system_action[(domain, 'Recommend')].append(['Name', choice['name']])
[1;31mRunning cells with 'Python 3.6.15 ('sysdial')' requires ipykernel package.
Run the following command to install 'ipykernel' into the Python environment.
Command: 'conda install -n sysdial ipykernel --update-deps --force-reinstall'
[1;31mRunning cells with 'Python 3.6.15 ('sysdial')' requires ipykernel package.
Run the following command to install 'ipykernel' into the Python environment.
Command: 'conda install -n sysdial ipykernel --update-deps --force-reinstall'
Podobnie jak w przypadku aktów użytkownika akty systemowe przekazywane do modułu NLG są czteroelementowymi listami złożonymi z:
- nazwy aktu systemowe,
- nazwy dziedziny, której dotyczy wypowiedź,
- nazwy slotu,
- wartości slotu.
Sprawdźmy jakie akty systemowe zwraca taktyka SimpleRulePolicy
w odpowiedzi na zmieniający się stan dialogu.
from convlab2.dialog_agent import PipelineAgent
dst.init_session()
policy = SimpleRulePolicy()
agent = PipelineAgent(nlu=None, dst=dst, policy=policy, nlg=None, name='sys')
[1;31mRunning cells with 'Python 3.6.15 ('sysdial')' requires ipykernel package.
Run the following command to install 'ipykernel' into the Python environment.
Command: 'conda install -n sysdial ipykernel --update-deps --force-reinstall'
[1;31mRunning cells with 'Python 3.6.15 ('sysdial')' requires ipykernel package.
Run the following command to install 'ipykernel' into the Python environment.
Command: 'conda install -n sysdial ipykernel --update-deps --force-reinstall'
agent.response([['Inform', 'Hotel', 'Price', 'cheap'], ['Inform', 'Hotel', 'Parking', 'yes']])
[1;31mRunning cells with 'Python 3.6.15 ('sysdial')' requires ipykernel package.
Run the following command to install 'ipykernel' into the Python environment.
Command: 'conda install -n sysdial ipykernel --update-deps --force-reinstall'
[1;31mRunning cells with 'Python 3.6.15 ('sysdial')' requires ipykernel package.
Run the following command to install 'ipykernel' into the Python environment.
Command: 'conda install -n sysdial ipykernel --update-deps --force-reinstall'
agent.response([['Inform', 'Hotel', 'Area', 'north']])
[1;31mRunning cells with 'Python 3.6.15 ('sysdial')' requires ipykernel package.
Run the following command to install 'ipykernel' into the Python environment.
Command: 'conda install -n sysdial ipykernel --update-deps --force-reinstall'
[1;31mRunning cells with 'Python 3.6.15 ('sysdial')' requires ipykernel package.
Run the following command to install 'ipykernel' into the Python environment.
Command: 'conda install -n sysdial ipykernel --update-deps --force-reinstall'
agent.response([['Request', 'Hotel', 'Area', '?']])
[1;31mRunning cells with 'Python 3.6.15 ('sysdial')' requires ipykernel package.
Run the following command to install 'ipykernel' into the Python environment.
Command: 'conda install -n sysdial ipykernel --update-deps --force-reinstall'
[1;31mRunning cells with 'Python 3.6.15 ('sysdial')' requires ipykernel package.
Run the following command to install 'ipykernel' into the Python environment.
Command: 'conda install -n sysdial ipykernel --update-deps --force-reinstall'
agent.response([['Inform', 'Hotel', 'Day', 'tuesday'], ['Inform', 'Hotel', 'People', '2'], ['Inform', 'Hotel', 'Stay', '4']])
[1;31mRunning cells with 'Python 3.6.15 ('sysdial')' requires ipykernel package.
Run the following command to install 'ipykernel' into the Python environment.
Command: 'conda install -n sysdial ipykernel --update-deps --force-reinstall'
[1;31mRunning cells with 'Python 3.6.15 ('sysdial')' requires ipykernel package.
Run the following command to install 'ipykernel' into the Python environment.
Command: 'conda install -n sysdial ipykernel --update-deps --force-reinstall'
Testy End-to-End
Na koniec przeprowadźmy dialog łącząc w potok nasze moduły
z modułami NLU i NLG dostępnymi dla MultiWOZ w środowisku ConvLab-2
.
from convlab2.nlu.svm.multiwoz import SVMNLU
from convlab2.nlg.template.multiwoz import TemplateNLG
nlu = SVMNLU()
nlg = TemplateNLG(is_user=False)
agent = PipelineAgent(nlu=nlu, dst=dst, policy=policy, nlg=nlg, name='sys')
[1;31mRunning cells with 'Python 3.6.15 ('sysdial')' requires ipykernel package.
Run the following command to install 'ipykernel' into the Python environment.
Command: 'conda install -n sysdial ipykernel --update-deps --force-reinstall'
[1;31mRunning cells with 'Python 3.6.15 ('sysdial')' requires ipykernel package.
Run the following command to install 'ipykernel' into the Python environment.
Command: 'conda install -n sysdial ipykernel --update-deps --force-reinstall'
agent.response("I need a cheap hotel with free parking .")
[1;31mRunning cells with 'Python 3.6.15 ('sysdial')' requires ipykernel package.
Run the following command to install 'ipykernel' into the Python environment.
Command: 'conda install -n sysdial ipykernel --update-deps --force-reinstall'
[1;31mRunning cells with 'Python 3.6.15 ('sysdial')' requires ipykernel package.
Run the following command to install 'ipykernel' into the Python environment.
Command: 'conda install -n sysdial ipykernel --update-deps --force-reinstall'
agent.response("Where it is located ?")
[1;31mRunning cells with 'Python 3.6.15 ('sysdial')' requires ipykernel package.
Run the following command to install 'ipykernel' into the Python environment.
Command: 'conda install -n sysdial ipykernel --update-deps --force-reinstall'
[1;31mRunning cells with 'Python 3.6.15 ('sysdial')' requires ipykernel package.
Run the following command to install 'ipykernel' into the Python environment.
Command: 'conda install -n sysdial ipykernel --update-deps --force-reinstall'
agent.response("I would prefer the hotel be in the north part of town .")
[1;31mRunning cells with 'Python 3.6.15 ('sysdial')' requires ipykernel package.
Run the following command to install 'ipykernel' into the Python environment.
Command: 'conda install -n sysdial ipykernel --update-deps --force-reinstall'
[1;31mRunning cells with 'Python 3.6.15 ('sysdial')' requires ipykernel package.
Run the following command to install 'ipykernel' into the Python environment.
Command: 'conda install -n sysdial ipykernel --update-deps --force-reinstall'
agent.response("Yeah , could you book me a room for 2 people for 4 nights starting Tuesday ?")
[1;31mRunning cells with 'Python 3.6.15 ('sysdial')' requires ipykernel package.
Run the following command to install 'ipykernel' into the Python environment.
Command: 'conda install -n sysdial ipykernel --update-deps --force-reinstall'
[1;31mRunning cells with 'Python 3.6.15 ('sysdial')' requires ipykernel package.
Run the following command to install 'ipykernel' into the Python environment.
Command: 'conda install -n sysdial ipykernel --update-deps --force-reinstall'
Zauważmy, ze nasza prosta taktyka dialogowa zawiera wiele luk, do których należą m.in.:
Niezdolność do udzielenia odpowiedzi na przywitanie, prośbę o pomoc lub restart.
Brak reguł dopytujących użytkownika o szczegóły niezbędne do dokonania rezerwacji takie, jak długość pobytu czy liczba osób.
Bardziej zaawansowane moduły zarządzania dialogiem zbudowane z wykorzystaniem reguł można znaleźć w
środowisku ConvLab-2
. Należą do nich m.in. monitor RuleDST oraz taktyka RuleBasedMultiwozBot.
Zadania
Zaimplementować w projekcie monitor stanu dialogu.
Zaimplementować w projekcie taktykę prowadzenia dialogu.
Termin: 24.05.2021, godz. 23:59.
Literatura
- Pawel Budzianowski, Tsung-Hsien Wen, Bo-Hsiang Tseng, Iñigo Casanueva, Stefan Ultes, Osman Ramadan, Milica Gasic, MultiWOZ - A Large-Scale Multi-Domain Wizard-of-Oz Dataset for Task-Oriented Dialogue Modelling. EMNLP 2018, pp. 5016-5026
- Cathy Pearl, Basic principles for designing voice user interfaces, https://www.oreilly.com/content/basic-principles-for-designing-voice-user-interfaces/ data dostępu: 21 marca 2021
- Cathy Pearl, Designing Voice User Interfaces, Excerpts from Chapter 5: Advanced Voice User Interface Design, https://www.uxmatters.com/mt/archives/2018/01/designing-voice-user-interfaces.php data dostępu: 21 marca 2021