Finishing up NLU module

This commit is contained in:
s495727 2024-05-22 23:45:33 +02:00
parent f0c7b481d1
commit 6ca7b66fb4
17 changed files with 284 additions and 171 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
__pycache__
slot-model*
frame-model*
nlu_cache

View File

@ -34,4 +34,13 @@ Agent powinien wykazywać elastyczność, adaptując się do potrzeb klienta, np
| inform | poinformowanie użytkownika, o przyjętej wartości slotu |
| offer | rekomendacja (restauracji) |
| request | pytanie użytkownika o wartość slotu |
| select | prośba o dokonanie wyboru spośród przedstawionych opcji |
| select | prośba o dokonanie wyboru spośród przedstawionych opcji |
# Obsługa projektu
- Python 3.10.12
- Instalacja dependencies `pip3 install -r requirements.txt`
- Centralna część systemu - uruchamiamy `python3 src/main.py`
- NLU:
- uczenie modeli od zera `python3 nlu_train.py`
- Ewaluacja `python3 evaluate.py`

View File

@ -2,11 +2,10 @@ import re
import os
import pandas as pd
import numpy as np
from nlu_utils import predict_multiple
from flair.models import SequenceTagger
from conllu import parse_incr
from flair.data import Corpus
from nlu_utils import conllu2flair, nolabel2o
from src.utils.nlu_utils import conllu2flair, nolabel2o, predict_multiple
# Frame model evaluation
frame_model = SequenceTagger.load('frame-model-prod/best-model.pt')

View File

@ -6,7 +6,7 @@ from flair.embeddings import CharacterEmbeddings
from flair.embeddings import FlairEmbeddings
from flair.models import SequenceTagger
from flair.trainers import ModelTrainer
from nlu_utils import conllu2flair, nolabel2o
from src.utils.nlu_utils import conllu2flair, nolabel2o
import torch
if torch.cuda.is_available():
@ -38,4 +38,4 @@ def train_model(label_type, field_parsers = {}):
if __name__ == '__main__':
train_model("frame")
train_model('slot', field_parsers={'slot': nolabel2o})
train_model('slot', field_parsers={'slot': nolabel2o})

5
requirements.txt Normal file
View File

@ -0,0 +1,5 @@
flair==0.13.1
conllu==4.5.3
pandas==1.5.3
numpy==1.26.4
torch==2.3.0

0
src/__init__.py Normal file
View File

View File

@ -1,26 +1,38 @@
from model.frame import Frame
from service.dialog_state_monitor import DialogStateMonitor
from service.dialog_policy import DialogPolicy
from service.natural_languag_understanding import NaturalLanguageUnderstanding
from service.natural_language_generation import NaturalLanguageGeneration
print("Natural language understanding, example:")
naturalLanguageUnderstanding = NaturalLanguageUnderstanding()
print(naturalLanguageUnderstanding.convert_text_to_frame("Cześć, jak masz na imię?"))
# initialize classes
nlu = NaturalLanguageUnderstanding() # NLU
monitor = DialogStateMonitor() # DSM
dialog_policy = DialogPolicy() # DP
language_generation = NaturalLanguageGeneration() # NLG
# Main loop
user_input = input("Możesz zacząć pisać.\n")
while True:
# NLU
frame = nlu.process_input(user_input)
print(frame)
# DSM
monitor.append(frame)
# DP
print(dialog_policy.next_dialogue_act(monitor.get_all()).act)
# NLG
response = language_generation.respond_to_name_query("Jak masz na imię?")
print(response)
if frame.act == "bye":
break
user_input = input(">\n")
# Example
print("Dialog state monitor, examples:")
monitor = DialogStateMonitor()
monitor.append(Frame('system', 'hello', []))
monitor.append(Frame('user', 'some_text', []))
print(monitor.get_all()[0].act)
print(monitor.get_last().act)
print("Dialog policy, next dialogue act:")
dialog_policy = DialogPolicy(monitor.get_all())
print(dialog_policy.next_dialogue_act().act)
print("Natural Language Generation example:")
agent = NaturalLanguageGeneration()
response = agent.respond_to_name_query("Jak masz na imię?")
print(response)

0
src/model/__init__.py Normal file
View File

View File

@ -1,7 +1,14 @@
from model.slot import Slot
from .slot import Slot
class Frame:
def __init__(self, source: str, act: str, slots: list[Slot]):
self.source = source
self.slots = slots
self.act = act
def __str__(self):
msg = f"Act: {self.act}, Slots: ["
for slot in self.slots:
msg += f"({slot}), "
msg += "]"
return msg

View File

@ -1,4 +1,7 @@
class Slot:
def __init__(self, name, value=None):
self.name = name
self.value = value
self.value = value
def __str__(self) -> str:
return f"Name: {self.name}, Value: {self.value}"

View File

@ -1,30 +1,30 @@
from flair.models import SequenceTagger
from nlu_utils import predict_single, predict_multiple, predict_and_annotate
# Exploratory tests
frame_model = SequenceTagger.load('frame-model/best-model.pt')
tests = [
'chciałbym zamówić pizzę',
'na godzinę 12',
'prosiłbym o pizzę z pieczarkami',
'to wszystko, jaka cena?',
'ile kosztuje pizza',
'do widzenia',
'tak',
'nie dziękuję',
'dodatkowy ser',
'pizzę barcelona bez cebuli',
]
# print("=== Exploratory tests - frame model ===")
for test in tests:
print(f"Sentence: {test}")
print(f"Single prediction: {predict_single(frame_model, test.split(), 'frame')}")
print(f"Multiple predictions: {predict_multiple(frame_model, test.split(), 'frame')}")
print(f"Annotated sentence: {predict_and_annotate(frame_model, test.split(), 'frame')}")
print("=== Exploratory tests - slot model ===")
slot_model = SequenceTagger.load('slot-model/final-model.pt')
for test in tests:
print(f"Sentence: {test}")
from flair.models import SequenceTagger
from utils.nlu_utils import predict_single, predict_multiple, predict_and_annotate
# Exploratory tests
frame_model = SequenceTagger.load('frame-model-prod/best-model.pt')
tests = [
'chciałbym zamówić pizzę',
'na godzinę 12',
'prosiłbym o pizzę z pieczarkami',
'to wszystko, jaka cena?',
'ile kosztuje pizza',
'do widzenia',
'tak',
'nie dziękuję',
'dodatkowy ser',
'pizzę barcelona bez cebuli',
]
print("=== Exploratory tests - frame model ===")
for test in tests:
print(f"Sentence: {test}")
print(f"Single prediction: {predict_single(frame_model, test.split(), 'frame')}")
print(f"Multiple predictions: {predict_multiple(frame_model, test.split(), 'frame')}")
print(f"Annotated sentence: {predict_and_annotate(frame_model, test.split(), 'frame')}")
print("=== Exploratory tests - slot model ===")
slot_model = SequenceTagger.load('slot-model-prod/best-model.pt')
for test in tests:
print(f"Sentence: {test}")
print(f"Prediction: {predict_and_annotate(slot_model, test.split(), 'slot')}")

0
src/service/__init__.py Normal file
View File

View File

@ -1,11 +1,8 @@
from model.frame import Frame
class DialogPolicy:
def __init__(self, frames: list[Frame]) -> None:
self.frames = frames
def next_dialogue_act(self) -> Frame:
if self.frames[-1].act == "welcomemsg":
def next_dialogue_act(self, frames: list[Frame]) -> Frame:
if frames[-1].act == "welcomemsg":
return Frame("system", "welcomemsg", [])
else:
return Frame("system", "canthelp", [])

View File

@ -1,14 +1,92 @@
from flair.models import SequenceTagger
from utils.nlu_utils import predict_single, predict_and_annotate
from model.frame import Frame, Slot
"""
ACTS:
inform/order
request/menu
inform/address
request/price
request/ingredients
request/sauce
inform/phone
inform/order-complete
request/time
request/size
welcomemsg
affirm
inform/delivery
inform/payment
request/delivery-price
bye
inform/time
request/drinks
inform/name
negate
SLOTS:
food
pizza
size
address
quantity
ingredient
payment-method
delivery
drink
ingredient/neg
name
phone
sauce
"""
class NaturalLanguageUnderstanding:
def __init__(self):
print("\n========================================================")
print("Models are loading, it may take a moment, please wait...")
print("========================================================\n")
dictionary = {
"Cześć," : "welcomemsg()",
"imię?" : "request(name)"
}
self.frame_model = SequenceTagger.load('frame-model-prod/best-model.pt')
self.slot_model = SequenceTagger.load('slot-model-prod/best-model.pt')
def convert_text_to_frame(self, text: str):
frame = ""
text = text.split(" ")
for word in text:
if(word in self.dictionary):
frame+=self.dictionary[word]+"&"
return frame[0:-1]
print("\n========================================================")
print("Models loaded. NLU system is ready.")
print("========================================================\n")
def __predict_intention(self, text: str):
return predict_single(self.frame_model, text.split(), 'frame')
def __predict_slot(self, text: str):
anootations = predict_and_annotate(self.slot_model, text.split(), 'slot')
current_slot = None
current_slot_value = ""
slots = []
for annotation in anootations:
form = annotation["form"]
slot = annotation["slot"]
if slot[0:2] == "B-":
if current_slot != None:
slots.append(Slot(name=current_slot, value=current_slot_value))
current_slot = slot[2:]
current_slot_value = form
elif slot[0:2] == "I-":
current_slot_value = current_slot_value + " " + form
elif slot == "O":
if current_slot != None:
slots.append(Slot(name=current_slot, value=current_slot_value))
current_slot = None
current_slot_value = ""
if current_slot != None:
slots.append(Slot(name=current_slot, value=current_slot_value))
return slots
def process_input(self, text: str):
act = self.__predict_intention(text)
slots = self.__predict_slot(text)
frame = Frame(source = 'user', act = act, slots = slots)
return frame

View File

@ -1,5 +1,5 @@
class NaturalLanguageGeneration:
def __init__(self, name):
def __init__(self):
self.name = ["Michał"]
def respond_to_name_query(self, question):

0
src/utils/__init__.py Normal file
View File

View File

@ -1,101 +1,100 @@
from flair.data import Sentence
from flair.datasets import FlairDatapointDataset
def nolabel2o(line, i):
return 'O' if line[i] == 'NoLabel' else line[i]
def conllu2flair(sentences, label=None):
if label == "frame":
return conllu2flair_frame(sentences, label)
else:
return conllu2flair_slot(sentences, label)
def conllu2flair_frame(sentences, label=None):
fsentences = []
for sentence in sentences:
tokens = [token["form"] for token in sentence]
fsentence = Sentence(' '.join(tokens), use_tokenizer=False)
for i in range(len(fsentence)):
fsentence[i:i+1].add_label(label, sentence[i][label])
fsentences.append(fsentence)
return FlairDatapointDataset(fsentences)
def conllu2flair_slot(sentences, label=None):
fsentences = []
for sentence in sentences:
fsentence = Sentence(' '.join(token['form'] for token in sentence), use_tokenizer=False)
start_idx = None
end_idx = None
tag = None
if label:
for idx, (token, ftoken) in enumerate(zip(sentence, fsentence)):
if token[label].startswith('B-'):
if start_idx is not None:
fsentence[start_idx:end_idx+1].add_label(label, tag)
start_idx = idx
end_idx = idx
tag = token[label][2:]
elif token[label].startswith('I-'):
end_idx = idx
elif token[label] == 'O':
if start_idx is not None:
fsentence[start_idx:end_idx+1].add_label(label, tag)
start_idx = None
end_idx = None
tag = None
if start_idx is not None:
fsentence[start_idx:end_idx+1].add_label(label, tag)
fsentences.append(fsentence)
return FlairDatapointDataset(fsentences)
def __predict(model, csentence):
fsentence = conllu2flair([csentence])[0]
model.predict(fsentence)
return fsentence
def __csentence(sentence, label_type):
if label_type == "frame":
return [{'form': word } for word in sentence]
else:
return [{'form': word, 'slot': 'O'} for word in sentence]
def predict_single(model, sentence, label_type):
csentence = __csentence(sentence, label_type)
fsentence = __predict(model, csentence)
intent = {}
for span in fsentence.get_spans(label_type):
tag = span.get_label(label_type).value
if tag in intent:
intent[tag] += 1
else:
intent[tag] = 1
return max(intent, key=intent.get)
def predict_multiple(model, sentence, label_type):
csentence = __csentence(sentence, label_type)
fsentence = __predict(model, csentence)
return set(span.get_label(label_type).value for span in fsentence.get_spans(label_type))
def predict_and_annotate(model, sentence, label_type):
csentence = __csentence(sentence, label_type)
fsentence = __predict(model, csentence)
for span in fsentence.get_spans(label_type):
tag = span.get_label(label_type).value
if label_type == "frame":
csentence[span.tokens[0].idx-1]['frame'] = tag
else:
csentence[span.tokens[0].idx - 1]['slot'] = f'B-{tag}'
for token in span.tokens[1:]:
csentence[token.idx - 1]['slot'] = f'I-{tag}'
from flair.data import Sentence
from flair.datasets import FlairDatapointDataset
def nolabel2o(line, i):
return 'O' if line[i] == 'NoLabel' else line[i]
def conllu2flair(sentences, label=None):
if label == "frame":
return conllu2flair_frame(sentences, label)
else:
return conllu2flair_slot(sentences, label)
def conllu2flair_frame(sentences, label=None):
fsentences = []
for sentence in sentences:
tokens = [token["form"] for token in sentence]
fsentence = Sentence(' '.join(tokens), use_tokenizer=False)
for i in range(len(fsentence)):
fsentence[i:i+1].add_label(label, sentence[i][label])
fsentences.append(fsentence)
return FlairDatapointDataset(fsentences)
def conllu2flair_slot(sentences, label=None):
fsentences = []
for sentence in sentences:
fsentence = Sentence(' '.join(token['form'] for token in sentence), use_tokenizer=False)
start_idx = None
end_idx = None
tag = None
if label:
for idx, (token, ftoken) in enumerate(zip(sentence, fsentence)):
if token[label].startswith('B-'):
if start_idx is not None:
fsentence[start_idx:end_idx+1].add_label(label, tag)
start_idx = idx
end_idx = idx
tag = token[label][2:]
elif token[label].startswith('I-'):
end_idx = idx
elif token[label] == 'O':
if start_idx is not None:
fsentence[start_idx:end_idx+1].add_label(label, tag)
start_idx = None
end_idx = None
tag = None
if start_idx is not None:
fsentence[start_idx:end_idx+1].add_label(label, tag)
fsentences.append(fsentence)
return FlairDatapointDataset(fsentences)
def __predict(model, csentence):
fsentence = conllu2flair([csentence])[0]
model.predict(fsentence)
return fsentence
def __csentence(sentence, label_type):
if label_type == "frame":
return [{'form': word } for word in sentence]
else:
return [{'form': word, 'slot': 'O'} for word in sentence]
def predict_single(model, sentence, label_type):
csentence = __csentence(sentence, label_type)
fsentence = __predict(model, csentence)
intent = {}
for span in fsentence.get_spans(label_type):
tag = span.get_label(label_type).value
if tag in intent:
intent[tag] += 1
else:
intent[tag] = 1
return max(intent, key=intent.get)
def predict_multiple(model, sentence, label_type):
csentence = __csentence(sentence, label_type)
fsentence = __predict(model, csentence)
return set(span.get_label(label_type).value for span in fsentence.get_spans(label_type))
def predict_and_annotate(model, sentence, label_type):
csentence = __csentence(sentence, label_type)
fsentence = __predict(model, csentence)
for span in fsentence.get_spans(label_type):
tag = span.get_label(label_type).value
if label_type == "frame":
csentence[span.tokens[0].idx-1]['frame'] = tag
else:
csentence[span.tokens[0].idx - 1]['slot'] = f'B-{tag}'
for token in span.tokens[1:]:
csentence[token.idx - 1]['slot'] = f'I-{tag}'
return csentence