systemy_dialogowe/notebooks/10-zarzadzanie-dialogiem-uczenie.ipynb
2023-04-21 17:42:07 +02:00

17 KiB

Zarządzanie dialogiem z wykorzystaniem technik uczenia maszynowego

Uczenie przez wzmacnianie

Zamiast ręcznie implementować zbiór reguł odpowiedzialnych za wyznaczenie akcji, którą powinien podjąć agent będąc w danym stanie, odpowiednią taktykę prowadzenia dialogu można zbudować, wykorzystując techniki uczenia maszynowego.

Obok metod uczenia nadzorowanego, które wykorzystaliśmy do zbudowania modelu NLU, do konstruowania taktyk prowadzenia dialogu wykorzystuje się również _uczenie przez wzmacnianie (ang. reinforcement learning).

W tym ujęciu szukać będziemy funkcji $Q*: S \times A \to R$, która dla stanu dialogu $s \in S$ oraz aktu dialogowego $a \in A$ zwraca nagrodę (ang. _reward) $r \in R$, tj. wartość rzeczywistą pozwalającą ocenić na ile podjęcie akcji $a$ w stanie $s$ jest korzystne.

Założymy również, że poszukiwana funkcja powinna maksymalizować _zwrot (ang. return), tj. skumulowaną nagrodę w toku prowadzonego dialogu, czyli dla tury $t_0$ cel uczenia powinien mieć postać:

$$ \sum_{t=t_0}^{\infty}{\gamma^{t-1}r_t} $$

gdzie:

  • $t$: tura agenta,

  • $r_t$: nagroda w turze $t$,

  • $\gamma \in [0, 1]$: współczynnik dyskontowy (w przypadku agentów dialogowych bliżej $1$ niż $0$, por. np. Rieser i Lemon (2011)).

Agent dialogowy w procesie uczenia przez wzmacnianie wchodzi w interakcję ze _środowiskiem, które dla akcji podejmowanej przez taktykę prowadzenia dialogu zwraca kolejny stan oraz nagrodę powiązaną z wykonaniem tej akcji w bieżącym stanie.

Sposób w jaki informacje pochodzące ze środowiska są wykorzystywane do znalezienia funkcji $Q*$ zależy od wybranej metody uczenia. W przykładzie przestawionym poniżej skorzystamy z algorytmu $DQN$ (Mnih i in., 2013) co oznacza, że:

  1. będziemy aproksymować funkcję $Q*$ siecią neuronową,

  2. wagi sieci będziemy wyznaczać korzystając z metody spadku gradientu.

Przykład

Ze względu na to, że implementacja algorytmu $DQN$ nie została jeszcze przystosowana do wersji 3.0 środowiska ConvLab skorzystamy z wersji 2.0.

!mkdir -p l10
%cd l10
!git clone --depth 1 https://github.com/thu-coai/ConvLab-2.git
%cd ConvLab-2
!pip install -e .
%cd ../..

Po zainstalowaniu środowiska ConvLab-2 należy zrestartować interpreter Pythona (opcja _Kernel -> Restart w Jupyter).

from convlab2.dialog_agent.agent import PipelineAgent
from convlab2.dialog_agent.env import Environment
from convlab2.dst.rule.multiwoz import RuleDST
from convlab2.policy.rule.multiwoz import RulePolicy
from convlab2.policy.dqn import DQN
from convlab2.policy.rlmodule import Memory
from convlab2.evaluator.multiwoz_eval import MultiWozEvaluator
import logging

logging.disable(logging.DEBUG)

# determinizacja obliczeń
import random
import torch
import numpy as np

np.random.seed(123)
random.seed(123)
torch.manual_seed(123)

Środowisko, z którym agent będzie wchodził w interakcje zawierać będzie symulator użytkownika wykorzystujący taktykę prowadzenia dialogu zbudowaną z wykorzystaniem reguł.

usr_policy = RulePolicy(character='usr')
usr_simulator = PipelineAgent(None, None, usr_policy, None, 'user')  # type: ignore

dst = RuleDST()
evaluator = MultiWozEvaluator()
env = Environment(None, usr_simulator, None, dst, evaluator)

Zobaczmy jak w _ConvLab-2 zdefiniowana jest nagroda w klasie Environment

%%script false --no-raise-error
#
# plik convlab2/dialog_agent/env.py
#
class Environment():

    # (...)

    def step(self, action):

        # (...)

        if self.evaluator:
            if self.evaluator.task_success():
                reward = 40
            elif self.evaluator.cur_domain and self.evaluator.domain_success(self.evaluator.cur_domain):
                reward = 5
            else:
                reward = -1
        else:
            reward = self.usr.get_reward()
        terminated = self.usr.is_terminated()

        return state, reward, terminated

Jak można zauważyć powyżej akcja, która prowadzi do pomyślnego zakończenia zadania uzyskuje nagrodę $40$, akcja która prowadzi do prawidłowego rozpoznania dziedziny uzyskuje nagrodę $5$, natomiast każda inna akcja uzyskuje "karę" $-1$. Taka definicja zwrotu premiuje krótkie dialogi prowadzące do pomyślnego wykonania zadania.

Sieć neuronowa, którą wykorzystamy do aproksymacji funkcji $Q*$ ma następującą architekturę

%%script false --no-raise-error
#
# plik convlab2/policy/rlmodule.py
# klasa EpsilonGreedyPolicy wykorzystywana w DQN
#
class EpsilonGreedyPolicy(nn.Module):
    def __init__(self, s_dim, h_dim, a_dim, epsilon_spec={'start': 0.1, 'end': 0.0, 'end_epoch': 200}):
        super(EpsilonGreedyPolicy, self).__init__()

        self.net = nn.Sequential(nn.Linear(s_dim, h_dim),
                                 nn.ReLU(),
                                 nn.Linear(h_dim, h_dim),
                                 nn.ReLU(),
                                 nn.Linear(h_dim, a_dim))
   # (...)
policy = DQN(is_train=True)

Każdy krok procedury uczenia składa się z dwóch etapów:

  1. Wygenerowania przy użyciu taktyki (metoda policy.predict) oraz środowiska (metoda env.step) _trajektorii, tj. sekwencji przejść pomiędzy stanami złożonych z krotek postaci:
    • stanu źródłowego,
    • podjętej akcji (aktu systemowego),
    • nagrody,
    • stanu docelowego,
    • znacznika końca dialogu.
# por. ConvLab-2/convlab2/policy/dqn/train.py
def sample(env, policy, batch_size, warm_up):
    buff = Memory()
    sampled_num = 0
    max_trajectory_len = 50

    while sampled_num < batch_size:
        # rozpoczęcie nowego dialogu
        s = env.reset()

        for t in range(max_trajectory_len):
            try:
                # podjęcie akcji przez agenta dialogowego
                a = policy.predict(s, warm_up=warm_up)

                # odpowiedź środowiska na podjętą akcje
                next_s, r, done = env.step(a)

                # dodanie krotki do zbioru danych
                buff.push(torch.Tensor(policy.vector.state_vectorize(s)).numpy(),       # stan źródłowy
                          policy.vector.action_vectorize(a),                            # akcja
                          r,                                                            # nagroda
                          torch.Tensor(policy.vector.state_vectorize(next_s)).numpy(),  # stan docelowy
                          0 if done else 1)                                             # znacznik końca

                s = next_s

                if done:
                    break
            except:
                break

        sampled_num += t

    return buff
  1. Wykorzystania wygenerowanych krotek do aktualizacji taktyki.

Funkcja train realizująca pojedynczy krok uczenia przez wzmacnianie ma następującą postać

def train(env, policy, batch_size, epoch, warm_up):
    print(f'epoch: {epoch}')
    buff = sample(env, policy, batch_size, warm_up)
    policy.update_memory(buff)
    policy.update(epoch)

Metoda update klasy DQN wykorzystywana do aktualizacji wag ma następującą postać

%%script false --no-raise-error
#
# plik convlab2/policy/dqn/dqn.py
# klasa DQN
#
class DQN(Policy):
    # (...)
    def update(self, epoch):
        total_loss = 0.
        for i in range(self.training_iter):
            round_loss = 0.
            # 1. batch a sample from memory
            batch = self.memory.get_batch(batch_size=self.batch_size)

            for _ in range(self.training_batch_iter):
                # 2. calculate the Q loss
                loss = self.calc_q_loss(batch)

                # 3. make a optimization step
                self.net_optim.zero_grad()
                loss.backward()
                self.net_optim.step()

                round_loss += loss.item()

    # (...)

Przebieg procesu uczenia zilustrujemy wykonując 10 iteracji. W każdej iteracji ograniczymy się do 100 przykładów.

epoch = 10
batch_size = 100

train(env, policy, batch_size, 0, warm_up=True)

for i in range(1, epoch):
    train(env, policy, batch_size, i, warm_up=False)

Sprawdźmy jakie akty systemowe zwraca taktyka DQN w odpowiedzi na zmieniający się stan dialogu.

from convlab2.dialog_agent import PipelineAgent
dst.init_session()
agent = PipelineAgent(nlu=None, dst=dst, policy=policy, nlg=None, name='sys')
agent.response([['Inform', 'Hotel', 'Price', 'cheap'], ['Inform', 'Hotel', 'Parking', 'yes']])
agent.response([['Inform', 'Hotel', 'Area', 'north']])
agent.response([['Request', 'Hotel', 'Area', '?']])
agent.response([['Inform', 'Hotel', 'Day', 'tuesday'], ['Inform', 'Hotel', 'People', '2'], ['Inform', 'Hotel', 'Stay', '4']])

Jakość wyuczonego modelu możemy ocenić mierząc tzw. wskaźnik sukcesu (ang. _task success rate), tj. stosunek liczby dialogów zakończonych powodzeniem do liczby wszystkich dialogów.

from convlab2.dialog_agent.session import BiSession

sess = BiSession(agent, usr_simulator, None, evaluator)
dialog_num = 100
task_success_num = 0
max_turn_num = 50

# por. ConvLab-2/convlab2/policy/evaluate.py
for dialog in range(dialog_num):
    random.seed(dialog)
    np.random.seed(dialog)
    torch.manual_seed(dialog)
    sess.init_session()
    sys_act = []
    task_success = 0

    for _ in range(max_turn_num):
        sys_act, _, finished, _ = sess.next_turn(sys_act)

        if finished is True:
            task_success = sess.evaluator.task_success()
            break

    print(f'dialog: {dialog:02} success: {task_success}')
    task_success_num += task_success

print('')
print(f'task success rate: {task_success_num/dialog_num:.2f}')

Uwaga: Chcąc uzyskać taktykę o skuteczności porównywalnej z wynikami przedstawionymi na stronie ConvLab-2 trzeba odpowiednio zwiększyć zarówno liczbę iteracji jak i liczbę przykładów generowanych w każdym przyroście. W celu przyśpieszenia procesu uczenia warto zrównoleglić obliczenia, jak pokazano w skrypcie train.py.

Literatura

  1. Rieser, V., Lemon, O., (2011). Reinforcement learning for adaptive dialogue systems: a data-driven methodology for dialogue management and natural language generation. (Theory and Applications of Natural Language Processing). Springer. https://doi.org/10.1007/978-3-642-24942-6

  2. Richard S. Sutton and Andrew G. Barto, (2018). Reinforcement Learning: An Introduction, Second Edition, MIT Press, Cambridge, MA http://incompleteideas.net/book/RLbook2020.pdf

  3. Volodymyr Mnih and Koray Kavukcuoglu and David Silver and Alex Graves and Ioannis Antonoglou and Daan Wierstra and Martin Riedmiller, (2013). Playing Atari with Deep Reinforcement Learning, NIPS Deep Learning Workshop, https://arxiv.org/pdf/1312.5602.pdf