{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "82a52d23",
   "metadata": {},
   "source": [
    "Parsing semantyczny z wykorzystaniem gramatyk\n",
    "=============================================\n",
    "\n",
    "Wartości slotów możemy wydobywać z wypowiedzi użytkownika korzystając z takich technik, jak:\n",
    "\n",
    " - wyszukiwanie słów kluczowych w tekście,\n",
    "\n",
    " - dopasowywanie wzorców zbudowanych przy użyciu wyrażeń regularnych,\n",
    "\n",
    " - parsery regułowe (temat dzisiejszych zajęć),\n",
    "\n",
    " - uczenie maszynowe (temat kolejnych zajęć)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4e864fb4",
   "metadata": {},
   "source": [
    "Przykłady parserów regułowych\n",
    "-----------------------------\n",
    "\n",
    " - [Phoenix](http://wiki.speech.cs.cmu.edu/olympus/index.php/Phoenix_Server) — parser gramatyk\n",
    "     bezkontekstowych whodzący w skład systemu dialogowego [Olympus](http://wiki.speech.cs.cmu.edu/olympus/index.php/Olympus)\n",
    "\n",
    " - Parsery [DCG](https://www.swi-prolog.org/pldoc/man?section=DCG) (Definite Clause Grammars) języka [Prolog](https://www.swi-prolog.org/)\n",
    "\n",
    " - [JSpeech Grammar Format](https://www.w3.org/TR/jsgf/) (JSGF)\n",
    "\n",
    "Przykład\n",
    "--------\n",
    "Zapiszmy w JSGF gramatykę semantyczną dla aktu dialogowego reprezentującego zamiar rezerwacji\n",
    "stolika w restauracji."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4c6b17fc",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%writefile book.jsgf\n",
    "#JSGF V1.0 UTF-8 pl;\n",
    "\n",
    "grammar book;\n",
    "\n",
    "public <rezerwuj> = chciałbym zarezerwować stolik <dzien_rezerwacji> <godzina_rezerwacji> <liczba_osob> ;\n",
    "\n",
    "<dzien_rezerwacji> = na <dzien> {day};\n",
    "\n",
    "<dzien> = dzisiaj | jutro | poniedziałek | wtorek | środę | czwartek | piątek | sobotę | niedzielę;\n",
    "\n",
    "<godzina_rezerwacji> = na [godzinę] <godzina_z_minutami> {hour};\n",
    "\n",
    "<godzina_z_minutami> = <godzina> [<minuty>];\n",
    "\n",
    "<godzina> = dziewiątą | dziesiątą | jedenastą | dwunastą;\n",
    "\n",
    "<minuty> = pietnaście | trzydzieści;\n",
    "\n",
    "<liczba_osob> = (na | dla) <liczba> {size} osób;\n",
    "\n",
    "<liczba> = dwie | dwóch | trzy | trzech | cztery | czterech | pięć | pieciu;\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7442f5b9",
   "metadata": {},
   "source": [
    "Parser akceptujący powyższą gramatykę utworzymy korzystając z biblioteki [pyjsgf](https://github.com/Danesprite/pyjsgf)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "927f7cac",
   "metadata": {},
   "outputs": [],
   "source": [
    "import jsgf\n",
    "\n",
    "book_grammar = jsgf.parse_grammar_file('book.jsgf')\n",
    "book_grammar"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7fbf718f",
   "metadata": {},
   "source": [
    "Wykorzystajmy gramatykę `book.jsgf` do analizy następującej wypowiedzi"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fe70432a",
   "metadata": {},
   "outputs": [],
   "source": [
    "utterance = 'chciałbym zarezerwować stolik na jutro na godzinę dwunastą trzydzieści na pięć osób'\n",
    "matched = book_grammar.find_matching_rules(utterance)\n",
    "matched"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4dd1ac3d",
   "metadata": {},
   "source": [
    "Reprezentację znaczenia można wydobyć ze sparsowanej wypowiedzi na wiele sposobów.  My do\n",
    "wydobywania slotów wykorzystamy mechanizm tagów JSGF a za nazwę aktu dialogowego przyjmiemy nazwę\n",
    "gramatyki. Wzorując się na [DSTC2](https://github.com/matthen/dstc) wynikową ramę zapiszemy korzystając ze słownika o polach `act` i `slots`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2b2bd6b7",
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_dialog_act(rule):\n",
    "    slots = []\n",
    "    get_slots(rule.expansion, slots)\n",
    "    return {'act': rule.grammar.name, 'slots': slots}\n",
    "\n",
    "def get_slots(expansion, slots):\n",
    "    if expansion.tag != '':\n",
    "        slots.append((expansion.tag, expansion.current_match))\n",
    "        return\n",
    "\n",
    "    for child in expansion.children:\n",
    "        get_slots(child, slots)\n",
    "\n",
    "    if not expansion.children and isinstance(expansion, jsgf.NamedRuleRef):\n",
    "        get_slots(expansion.referenced_rule.expansion, slots)\n",
    "\n",
    "get_dialog_act(matched[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2fca2fc3",
   "metadata": {},
   "source": [
    "Łącząc powyższe funkcje możemy zbudować prosty moduł NLU."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "87a917a7",
   "metadata": {},
   "outputs": [],
   "source": [
    "def nlu(utterance):\n",
    "    matched = book_grammar.find_matching_rules(utterance)\n",
    "\n",
    "    if matched:\n",
    "        return get_dialog_act(matched[0])\n",
    "    else:\n",
    "        return {'act': 'null', 'slots': []}\n",
    "\n",
    "nlu('chciałbym zarezerwować stolik na jutro na godzinę dziesiątą dla trzech osób')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9e801dde",
   "metadata": {},
   "source": [
    "Problemy\n",
    "--------\n",
    "\n",
    " - Co z normalizacją wyrażeń liczbowych takich, jak godziny, daty czy numery telefonów?\n",
    "\n",
    " - Co w przypadku gdy więcej niż jedna reguła zostanie dopasowana?"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c8289023",
   "metadata": {},
   "source": [
    "Zadanie\n",
    "-------\n",
    "Zaimplementować analizator języka naturalnego (NLU) na potrzeby realizowanego agenta dialogowego.\n",
    "\n",
    "Moduł powinien być zbudowany z wykorzystaniem parsingu regułowego i/lub technik uczenia maszynowego.\n",
    "\n",
    "Przygotować skrypt `evaluate.py` wyznaczający *dokładność* (ang. accuracy) analizatora względem zgromadzonego korpusu eksperymentalnego,\n",
    "tj. stosunek liczby wypowiedzi użytkownika, w których akty dialogowe zostały rozpoznane prawidłowo do liczby wszystkich wypowiedzi użytkownika w korpusie.\n",
    "\n",
    "Analizator języka naturalnego umieścić w gałęzi `master` repozytorium projektowego. Skrypt `evaluate.py` umieścić w katalogu głównym tej gałęzi."
   ]
  }
 ],
 "metadata": {
  "jupytext": {
   "cell_metadata_filter": "-all",
   "main_language": "python",
   "notebook_metadata_filter": "-all"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}