34 KiB
Systemy Dialogowe
8. Parsing semantyczny z wykorzystaniem technik uczenia maszynowego [laboratoria]
Marek Kubis (2021)
Parsing semantyczny z wykorzystaniem technik uczenia maszynowego
Wprowadzenie
Problem wykrywania slotów i ich wartości w wypowiedziach użytkownika można sformułować jako zadanie polegające na przewidywaniu dla poszczególnych słów etykiet wskazujących na to czy i do jakiego slotu dane słowo należy.
chciałbym zarezerwować stolik na jutro**/day** na godzinę dwunastą**/hour** czterdzieści**/hour** pięć**/hour** na pięć**/size** osób
Granice slotów oznacza się korzystając z wybranego schematu etykietowania.
Schemat IOB
Prefix | Znaczenie |
---|---|
I | wnętrze slotu (inside) |
O | poza slotem (outside) |
B | początek slotu (beginning) |
chciałbym zarezerwować stolik na jutro**/B-day** na godzinę dwunastą**/B-hour** czterdzieści**/I-hour** pięć**/I-hour** na pięć**/B-size** osób
Schemat IOBES
Prefix | Znaczenie |
---|---|
I | wnętrze slotu (inside) |
O | poza slotem (outside) |
B | początek slotu (beginning) |
E | koniec slotu (ending) |
S | pojedyncze słowo (single) |
chciałbym zarezerwować stolik na jutro**/S-day** na godzinę dwunastą**/B-hour** czterdzieści**/I-hour** pięć**/E-hour** na pięć**/S-size** osób
Jeżeli dla tak sformułowanego zadania przygotujemy zbiór danych złożony z wypowiedzi użytkownika z oznaczonymi slotami (tzw. _zbiór uczący), to możemy zastosować techniki (nadzorowanego) uczenia maszynowego w celu zbudowania modelu annotującego wypowiedzi użytkownika etykietami slotów.
Do zbudowania takiego modelu można wykorzystać między innymi:
warunkowe pola losowe (Lafferty i in.; 2001),
rekurencyjne sieci neuronowe, np. sieci LSTM (Hochreiter i Schmidhuber; 1997),
transformery (Vaswani i in., 2017).
Przykład
Skorzystamy ze zbioru danych przygotowanego przez Schustera (2019).
Zbiór ten gromadzi wypowiedzi w trzech językach opisane slotami dla dwunastu ram należących do trzech dziedzin Alarm
, Reminder
oraz Weather
. Dane wczytamy korzystając z biblioteki conllu.
from conllu import parse_incr
fields = ['id', 'form', 'frame', 'slot']
def nolabel2o(line, i):
return 'O' if line[i] == 'NoLabel' else line[i]
# pathTrain = '../tasks/zad8/en/train-en.conllu'
# pathTest = '../tasks/zad8/en/test-en.conllu'
pathTrain = '../tasks/zad8/pl/train.conllu'
pathTest = '../tasks/zad8/pl/test.conllu'
with open(pathTrain, encoding="UTF-8") as trainfile:
i=0
for line in trainfile:
print(line)
i+=1
if i==15: break
trainset = list(parse_incr(trainfile, fields=fields, field_parsers={'slot': nolabel2o}))
with open(pathTest, encoding="UTF-8") as testfile:
testset = list(parse_incr(testfile, fields=fields, field_parsers={'slot': nolabel2o}))
# text: halo # intent: hello # slots: 1 halo hello NoLabel # text: chaciałbym pójść na premierę filmu jakie premiery są w tym tygodniu # intent: reqmore # slots: 1 chaciałbym reqmore NoLabel 2 pójść reqmore NoLabel 3 na reqmore NoLabel 4 premierę reqmore NoLabel 5 filmu reqmore NoLabel 6 jakie reqmore B-goal 7 premiery reqmore I-goal
Zobaczmy kilka przykładowych wypowiedzi z tego zbioru.
from tabulate import tabulate
tabulate(trainset[1], tablefmt='html')
1 | wybieram | inform | O |
2 | batmana | inform | B-title |
tabulate(trainset[16], tablefmt='html')
1 | chcę | inform | O |
2 | zarezerwować | inform | B-goal |
3 | bilety | inform | O |
tabulate(trainset[20], tablefmt='html')
1 | chciałbym | inform | O |
2 | anulować | inform | O |
3 | rezerwację | inform | O |
4 | biletu | inform | O |
Budując model skorzystamy z architektury opartej o rekurencyjne sieci neuronowe zaimplementowanej w bibliotece flair (Akbik i in. 2018).
from flair.data import Corpus, Sentence, Token
from flair.datasets import SentenceDataset
from flair.embeddings import StackedEmbeddings
from flair.embeddings import WordEmbeddings
from flair.embeddings import CharacterEmbeddings
from flair.embeddings import FlairEmbeddings
from flair.models import SequenceTagger
from flair.trainers import ModelTrainer
from flair.datasets import DataLoader
# determinizacja obliczeń
import random
import torch
random.seed(42)
torch.manual_seed(42)
if torch.cuda.is_available():
torch.cuda.manual_seed(0)
torch.cuda.manual_seed_all(0)
torch.backends.cudnn.enabled = False
torch.backends.cudnn.benchmark = False
torch.backends.cudnn.deterministic = True
Dane skonwertujemy do formatu wykorzystywanego przez flair
, korzystając z następującej funkcji.
def conllu2flair(sentences, label1=None, label2=None):
fsentences = []
for sentence in sentences:
fsentence = Sentence()
for token in sentence:
ftoken = Token(token['form'])
if label1:
if label2:
ftoken.add_tag(label1, token[label1] + "/" + token[label2])
else:
ftoken.add_tag(label1, token[label1])
fsentence.add_token(ftoken)
fsentences.append(fsentence)
return SentenceDataset(fsentences)
corpus = Corpus(train=conllu2flair(trainset, 'slot', "frame"), test=conllu2flair(testset, 'slot', "frame"))
print(corpus)
tag_dictionary = corpus.make_tag_dictionary(tag_type='slot')
print(tag_dictionary)
Corpus: 346 train + 38 dev + 32 test sentences Dictionary with 78 tags: <unk>, O, O/reqmore, B-interval/reqmore, I-interval/reqmore, O/inform, B-title/inform, B-date/inform, I-date/inform, B-time/inform, B-quantity/inform, B-area/inform, I-area/inform, B-goal/inform, O/bye, O/hello, O/reqmore inform, B-goal/reqmore inform, I-goal/reqmore inform, B-date/reqmore inform, B-interval/reqmore inform, O/null, O/help, B-goal/reqmore, I-goal/reqmore, B-title/reqmore, B-title/reqmore inform, I-title/reqmore inform, O/ack, O/reqalts
Nasz model będzie wykorzystywał wektorowe reprezentacje słów (zob. Word Embeddings).
embedding_types = [
WordEmbeddings('pl'),
FlairEmbeddings('polish-forward'),
FlairEmbeddings('polish-backward'),
CharacterEmbeddings(),
]
embeddings = StackedEmbeddings(embeddings=embedding_types)
tagger = SequenceTagger(hidden_size=256, embeddings=embeddings,
tag_dictionary=tag_dictionary,
tag_type='slot', use_crf=True)
Zobaczmy jak wygląda architektura sieci neuronowej, która będzie odpowiedzialna za przewidywanie slotów w wypowiedziach.
print(tagger)
SequenceTagger( (embeddings): StackedEmbeddings( (list_embedding_0): WordEmbeddings('pl') (list_embedding_1): FlairEmbeddings( (lm): LanguageModel( (drop): Dropout(p=0.25, inplace=False) (encoder): Embedding(1602, 100) (rnn): LSTM(100, 2048) (decoder): Linear(in_features=2048, out_features=1602, bias=True) ) ) (list_embedding_2): FlairEmbeddings( (lm): LanguageModel( (drop): Dropout(p=0.25, inplace=False) (encoder): Embedding(1602, 100) (rnn): LSTM(100, 2048) (decoder): Linear(in_features=2048, out_features=1602, bias=True) ) ) (list_embedding_3): CharacterEmbeddings( (char_embedding): Embedding(275, 25) (char_rnn): LSTM(25, 25, bidirectional=True) ) ) (word_dropout): WordDropout(p=0.05) (locked_dropout): LockedDropout(p=0.5) (embedding2nn): Linear(in_features=4446, out_features=4446, bias=True) (rnn): LSTM(4446, 256, batch_first=True, bidirectional=True) (linear): Linear(in_features=512, out_features=78, bias=True) (beta): 1.0 (weights): None (weight_tensor) None )
Wykonamy dziesięć iteracji (epok) uczenia a wynikowy model zapiszemy w katalogu slot-model
.
modelPath = 'slot-model/final-model.pt'
from os.path import exists
fileExists = exists(modelPath)
if(not fileExists):
trainer = ModelTrainer(tagger, corpus)
trainer.train('slot-model',
learning_rate=0.1,
mini_batch_size=32,
max_epochs=10,
train_with_dev=False)
Jakość wyuczonego modelu możemy ocenić, korzystając z zaraportowanych powyżej metryk, tj.:
_tp (true positives)
liczba słów oznaczonych w zbiorze testowym etykietą $e$, które model oznaczył tą etykietą
_fp (false positives)
liczba słów nieoznaczonych w zbiorze testowym etykietą $e$, które model oznaczył tą etykietą
_fn (false negatives)
liczba słów oznaczonych w zbiorze testowym etykietą $e$, którym model nie nadał etykiety $e$
_precision
$$\frac{tp}{tp + fp}$$
_recall
$$\frac{tp}{tp + fn}$$
$F_1$
$$\frac{2 \cdot precision \cdot recall}{precision + recall}$$
_micro $F_1$
$F_1$ w którym $tp$, $fp$ i $fn$ są liczone łącznie dla wszystkich etykiet, tj. $tp = \sum_{e}{{tp}_e}$, $fn = \sum{e}{{fn}e}$, $fp = \sum{e}{{fp}_e}$
_macro $F_1$
średnia arytmetyczna z $F_1$ obliczonych dla poszczególnych etykiet z osobna.
Wyuczony model możemy wczytać z pliku korzystając z metody load
.
model = SequenceTagger.load(modelPath)
2022-05-22 15:25:19,970 loading file slot-model/final-model.pt
Wczytany model możemy wykorzystać do przewidywania slotów w wypowiedziach użytkownika, korzystając
z przedstawionej poniżej funkcji predict
.
def predict(model, sentence):
csentence = [{'form': word} for word in sentence]
fsentence = conllu2flair([csentence])[0]
model.predict(fsentence)
return [(token, ftoken.get_tag('slot').value) for token, ftoken in zip(sentence, fsentence)]
predict(model, 'co gracie obecnie'.split())
[('co', 'O/reqmore'), ('gracie', 'O/reqmore'), ('obecnie', 'O/reqmore')]
Jak pokazuje przykład poniżej model wyuczony tylko na 100 przykładach popełnia w dosyć prostej
wypowiedzi błąd etykietując słowo alarm
tagiem B-weather/noun
.
tabulate(predict(model, 'kiedy gracie film zorro'.split()), tablefmt='html')
kiedy | O/reqmore |
gracie | O/reqmore |
film | O/reqmore |
zorro | O/reqmore |
# evaluation
def precision(tpScore, fpScore):
return float(tpScore) / (tpScore + fpScore)
def recall(tpScore, fnScore):
return float(tpScore) / (tpScore + fnScore)
def f1(precision, recall):
return 2 * precision * recall/(precision + recall)
def eval():
tp = 0
fp = 0
fn = 0
sentences = [sentence for sentence in testset]
for sentence in sentences:
# get sentence as terms list
termsList = [w["form"] for w in sentence]
# predict tags
predTags = [tag[1] for tag in predict(model, termsList)]
expTags = [token["slot"] + "/" + token["frame"] for token in sentence]
for i in range(len(predTags)):
if (expTags[i][0] == "O" and expTags[i] != predTags[i]):
fp += 1
elif ((expTags[i][0] != "O") & (predTags[i][0] == "O")):
fn += 1
elif ((expTags[i][0] != "O") & (predTags[i] == expTags[i])):
tp += 1
precisionScore = precision(tp, fp)
recallScore = recall(tp, fn)
f1Score = f1(precisionScore, recallScore)
print("stats: ")
print("precision: ", precisionScore)
print("recall: ", recallScore)
print("f1: ", f1Score)
eval()
[1;31m---------------------------------------------------------------------------[0m [1;31mNameError[0m Traceback (most recent call last) [1;32mc:\Develop\wmi\AITECH\sem1\Systemy dialogowe\lab\08-parsing-semantyczny-uczenie(zmodyfikowany).ipynb Cell 25'[0m in [0;36m<cell line: 40>[1;34m()[0m [0;32m <a href='vscode-notebook-cell:/c%3A/Develop/wmi/AITECH/sem1/Systemy%20dialogowe/lab/08-parsing-semantyczny-uczenie%28zmodyfikowany%29.ipynb#ch0000024?line=36'>37</a>[0m [39mprint[39m([39m"[39m[39mrecall: [39m[39m"[39m, recallScore) [0;32m <a href='vscode-notebook-cell:/c%3A/Develop/wmi/AITECH/sem1/Systemy%20dialogowe/lab/08-parsing-semantyczny-uczenie%28zmodyfikowany%29.ipynb#ch0000024?line=37'>38</a>[0m [39mprint[39m([39m"[39m[39mf1: [39m[39m"[39m, f1Score) [1;32m---> <a href='vscode-notebook-cell:/c%3A/Develop/wmi/AITECH/sem1/Systemy%20dialogowe/lab/08-parsing-semantyczny-uczenie%28zmodyfikowany%29.ipynb#ch0000024?line=39'>40</a>[0m [39meval[39;49m() [1;32mc:\Develop\wmi\AITECH\sem1\Systemy dialogowe\lab\08-parsing-semantyczny-uczenie(zmodyfikowany).ipynb Cell 25'[0m in [0;36meval[1;34m()[0m [0;32m <a href='vscode-notebook-cell:/c%3A/Develop/wmi/AITECH/sem1/Systemy%20dialogowe/lab/08-parsing-semantyczny-uczenie%28zmodyfikowany%29.ipynb#ch0000024?line=13'>14</a>[0m fp [39m=[39m [39m0[39m [0;32m <a href='vscode-notebook-cell:/c%3A/Develop/wmi/AITECH/sem1/Systemy%20dialogowe/lab/08-parsing-semantyczny-uczenie%28zmodyfikowany%29.ipynb#ch0000024?line=14'>15</a>[0m fn [39m=[39m [39m0[39m [1;32m---> <a href='vscode-notebook-cell:/c%3A/Develop/wmi/AITECH/sem1/Systemy%20dialogowe/lab/08-parsing-semantyczny-uczenie%28zmodyfikowany%29.ipynb#ch0000024?line=15'>16</a>[0m sentences [39m=[39m [sentence [39mfor[39;00m sentence [39min[39;00m testset] [0;32m <a href='vscode-notebook-cell:/c%3A/Develop/wmi/AITECH/sem1/Systemy%20dialogowe/lab/08-parsing-semantyczny-uczenie%28zmodyfikowany%29.ipynb#ch0000024?line=16'>17</a>[0m [39mfor[39;00m sentence [39min[39;00m sentences: [0;32m <a href='vscode-notebook-cell:/c%3A/Develop/wmi/AITECH/sem1/Systemy%20dialogowe/lab/08-parsing-semantyczny-uczenie%28zmodyfikowany%29.ipynb#ch0000024?line=17'>18</a>[0m [39m# get sentence as terms list[39;00m [0;32m <a href='vscode-notebook-cell:/c%3A/Develop/wmi/AITECH/sem1/Systemy%20dialogowe/lab/08-parsing-semantyczny-uczenie%28zmodyfikowany%29.ipynb#ch0000024?line=18'>19</a>[0m termsList [39m=[39m [w[[39m"[39m[39mform[39m[39m"[39m] [39mfor[39;00m w [39min[39;00m sentence] [1;31mNameError[0m: name 'testset' is not defined
Literatura
- Sebastian Schuster, Sonal Gupta, Rushin Shah, Mike Lewis, Cross-lingual Transfer Learning for Multilingual Task Oriented Dialog. NAACL-HLT (1) 2019, pp. 3795-3805
- John D. Lafferty, Andrew McCallum, and Fernando C. N. Pereira. 2001. Conditional Random Fields: Probabilistic Models for Segmenting and Labeling Sequence Data. In Proceedings of the Eighteenth International Conference on Machine Learning (ICML '01). Morgan Kaufmann Publishers Inc., San Francisco, CA, USA, 282–289, https://repository.upenn.edu/cgi/viewcontent.cgi?article=1162&context=cis_papers
- Sepp Hochreiter and Jürgen Schmidhuber. 1997. Long Short-Term Memory. Neural Comput. 9, 8 (November 15, 1997), 1735–1780, https://doi.org/10.1162/neco.1997.9.8.1735
- Ashish Vaswani, Noam Shazeer, Niki Parmar, Jakob Uszkoreit, Llion Jones, Aidan N. Gomez, Lukasz Kaiser, Illia Polosukhin, Attention is All you Need, NIPS 2017, pp. 5998-6008, https://arxiv.org/abs/1706.03762
- Alan Akbik, Duncan Blythe, Roland Vollgraf, Contextual String Embeddings for Sequence Labeling, Proceedings of the 27th International Conference on Computational Linguistics, pp. 1638–1649, https://www.aclweb.org/anthology/C18-1139.pdf