Finishing up NLU module
This commit is contained in:
parent
f0c7b481d1
commit
6ca7b66fb4
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
__pycache__
|
||||
slot-model*
|
||||
frame-model*
|
||||
nlu_cache
|
11
README.md
11
README.md
@ -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`
|
||||
|
@ -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')
|
||||
|
@ -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
5
requirements.txt
Normal 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
0
src/__init__.py
Normal file
48
src/main.py
48
src/main.py
@ -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
0
src/model/__init__.py
Normal 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
|
||||
|
@ -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}"
|
@ -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
0
src/service/__init__.py
Normal 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", [])
|
||||
|
@ -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
|
@ -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
0
src/utils/__init__.py
Normal 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
|
Loading…
Reference in New Issue
Block a user