Інструменти для Natural Language Understanding: поради, особливості роботи, та українська мова в NLP
Привіт! Мене звати Ян Бутельський, я NLP-фахівець в сфері розробки діалогових систем. Вже сьомий рік допомагаю штучному інтелекту зрозуміти людський.
Як і обіцяв, продовжую серію публікацій про NLP в контексті розробки діалогової системи. Головна мета — описати свій власний досвід роботи з NLU-модулем та детально проаналізувати наявні Python бібліотеки (SPACY, STANZA, FLAIR) для якісної та швидкої розробки NLU-модуля. З першою частиною цієї теми ви можете ознайомитись в цій публікації.
Особливості та переваги Spacy
Почнемо з бібліотеки Spacy, яка зарекомендувала себе як незамінний інструмент в арсеналі багатьох інженерів та науковців. Одразу скажу, що використовував її як основний пайплайн на декількох NLP-проєктах. Системи, які були побудовані на її базі обробляють сотні тисяч текстових документів в день, без memory leaks та ексепшенів :) З власного досвіду можу сказати, що її основними перевагами є швидкодія, оптимізація роботи під CPU, а не тільки GPU, надзвичайно велике ком’юніті, ну і звісно простий та зручний інтерфейс, про який я розповім вам в цій статті з прикладами.
На малюнку зображені основні елементи пайплайну Spacy.
Першим елементом в пайплайні є неструктурований текст. Для початку роботи треба лише встановити Spacy та завантажити необхідну мовну модель. Перелік доступних моделей можна знайти за цим посиланням.
!pip install spacy !python -m spacy download en_core_web_lg import spacy nlp = spacy.load("en_core_web_lg") text = ("When Sebastian Thrun started working on self-driving cars at Google in 2007, few people outside of the company took him seriously.I can tell you very senior CEOs of major American car companies would shake my hand and turn away because I wasn't worth talking to, said Thrun, in an interview with Recode earlier this week.") doc = nlp(text)
Закодовуючи текст на одній з мовних моделей англійської мови, ми одразу отримаємо доступ до всіх доступних NLU-інструментів Spacy, за допомогою яких можна почати розробку власного NLU-модуля для діалогової системи. Одним з найбільш показових є модуль NER, який допоможе знайти сутності, щоб розпізнати і заповнити необхідні слоти для розпізнавання інтенту.
На прикладі нижче показно, які сутності витягнуло Spacy з прикладу тексту вище. Як на мене, досить непоганий результат.
Стрічка коду нижче дає перелік всіх доступних сутностей для цієї мовної моделі.
nlp.pipe_labels['ner'] ['CARDINAL', 'DATE', 'EVENT', 'FAC', 'GPE', 'LANGUAGE', 'LAW', 'LOC', 'MONEY', 'NORP', 'ORDINAL', 'ORG', 'PERCENT', 'PERSON', 'PRODUCT', 'QUANTITY', 'TIME', 'WORK_OF_ART']
Також хочеться звернути увагу на функціонал, який допоможе визначити подібність речень, або слів за допомогою векторного простору, який надає Spacy з коробки.
doc1 = nlp("I like salty fries and hamburgers.") doc2 = nlp("Fast food tastes very good.") # Similarity of two documents print(doc1, "<->", doc2, doc1.similarity(doc2)) # Similarity of tokens and spans french_fries = doc1[2:4] burgers = doc1[5] print(french_fries, "<->", burgers, french_fries.similarity(burgers))
Точність кожного з модулей Spacy — дивіться тут.
Варто додати, що Spacy дозволяє глибоко кастомізувати кожний зі своїх елементів пайплайну, зокрема модуль NER, тобто це дозволяє тренувати свої кастомні сутності.
Загалом раджу всім спробувати Spacy для роботи з текстами та уважно дослідити всі властивості Spacy.doc, Spacy.span, та Spacy.token об’єктів.
Властивості Stanza
Наступна NLU-бібліотека, на яку хотілося б звернути увагу — це Stanza. Як на мене, Stanza та Spacy досить подібні за своєю філософією та NLU можливостями для використання однієї з них у розробці діалогової системи.
На малюнку нижче — зображення основні елементи пайплайну Stanza.
Однією з переваг Stanza над Spacy є набагато більша кількість мовних моделей для різних мов світу — понад 60 мов. Зокрема серед них є українська мова, що мене дуже тішить :) На прикладі української мови спробуємо розібратись, яку лінгвістичну інформацію надає нам Stanza.
На прикладі слова «народився» видно, наскільки повною є лінгвістична інформація про кожне вибране слово з прикладу. Також можна дуже просто визначити сутності, які розпізнала українська мовна модель.
text = "Ян Бутельський народився у місті Львів" import stanza stanza.download('uk') nlp = stanza.Pipeline('uk') doc = nlp(text) print(doc) print(doc.entities) { "id": 3, "text": "народився", "lemma": "народитися", "upos": "VERB", "xpos": "Vmeis-sm", "feats": "Aspect=Perf|Gender=Masc|Mood=Ind|Number=Sing|Tense=Past|VerbForm=Fin", "head": 0, "deprel": "root", "start_char": 15, "end_char": 24, "ner": "O" }, [{ "text": "Бутельський", "type": "PERS", "start_char": 3, "end_char": 14 }, { "text": "Львів", "type": "LOC", "start_char": 33, "end_char": 38 }]
Приклади вище свідчать про те, що Stanza може стати досить непоганою альтернативою Spacy для розробки власно NLU-модуля. Відповідь на запитання «Що вибрати?» досить неочевидна, тому раджу всім спробувати обидві та детально ознайомитись з документацією в посиланнях, що наведені вище. З власного досвіду хочу сказати, що активніше використовую Spacy, напевно більше сподобався вебсайт :)
Приклад використання Flair
Останнє, про що хотілося б поговорити в цій статті, це можливість особисто натренувати модель зі специфічними сутностями, які не є наявними в пайплайнах бібліотек Spacy та Stanza. Звичайно, кожна з цих бібліотек має функціонал для цього, проте він не до кінця оптимальний та гнучкий. Водночас, якщо маєте бажання, можна спробувати різні рішення, адже, можливо, саме та чи інша бібліотека якраз підійде для вирішення ваших завдань!
Щоб ще більше розширити кругозір в різномаїтті NLU-бібліотек, потрібно згадати про Flair. Особисто для мене вона стала одним з найкращих інструментів для розробки класифікаторів на базі Pytorch. Щоправда, Flair — це надзвичайно зручна та гнучка обгортка навколо Pytorch. Вона дозволяє з коробки використовувати широку кількість мовних моделей, які можна легко та швидко дотренувати своїми власними даними. Вам на вибір дають можливість роботи як і рекурентними мережами, так і троформерами, зокрема з BERT.
Приклад коду нижче показує, що інтерфейс Flair також дуже простий та зрозумілий в роботі. Для прикладу використаємо той самий приклад тексту, що й і для Spacy.
from flair.data import Sentence from flair.models import SequenceTagger # make a sentence sentence = Sentence(text) # load the NER tagger tagger = SequenceTagger.load('ner') # run NER over sentence tagger.predict(sentence) print(sentence) print('The following NER tags are found:') # iterate over entities and print for entity in sentence.get_spans('ner'): print(entity) The following NER tags are found: Span [2,3]: "Sebastian Thrun" [− Labels: PER (0.9996)] Span [10]: "Google" [− Labels: ORG (0.9988)] Span [31]: "American" [− Labels: MISC (0.9965)] Span [50]: "Thrun" [− Labels: PER (0.9999)] Span [56]: "Recode" [− Labels: ORG (0.9848)]
Отже, результат трошки гірший, ніж у Spacy, проте цікавою властивістю є впевненість моделі стосовно кожної сутності, в той же час, однією з беззаперечних переваг саме Spacy перед Flair, швидкодія — Spacy від 3 до 5 разів швидша, коли робить tagger.predict. Це досить значна перевага.
Тренувальний процес Flair
Наступне, на чому хотілося б зосередити увагу, це пайплайн тренування Flair моделі. Давайте спочатку виберемо середовища для тренування. Це можна робити локально на CPU і GPU. З власного досвіду, Pytorch більше любить GPU :) Як каже мій власний досвід, оптимальним вибором є якийсь клауд-сервіс на кшталт Colab. Це дуже зручний застосунок — швидко, зручно, все зберігається в хмарі, можна тренувати ледь не з браузера телефону — СПРОБУЙТЕ.
Приклад нижче зображує основні кроки тренувального процесу.
Перший крок — це, звісно, так званий лейблінг данних — дуже довгий та клопіткий процес, оскільки від нього найбільше залежить якість роботи майбутньої моделі. Опис процесу лейблінгу заслуговує на окрему публікацію, тому детально на ньому зупинятись не буду. Лише скажу, що класичний підхід — це BIO-анотація, трохи більше деталей описано в моїй попередній публікації. У прикладі використовуються вже заздалегідь промарковані дані, раджу розібратись з форматом без поспіху. Головна його особливість — те, що кожне слово-токін промарковане необхідною лейбою, вказаною з нового рядка, а кожне нове речення розділене \n.
Отже, наша майбутня модель буде намагатись класифікувати кожний токін відповідною лейбою, визначити, де початок та кінець сутності та речення. З прикладу нижче легко розібратись з наступними процесами пайплайну, такими як використання вже готових імбедінгів слів, які, до речі, Flair дозволяє комбінувати, але не забувайте, що це може добряче збільшити час тренування. Для прикладу беремо LSTM-модель з досить стандартними праметрами, тут вже хто хоче може поекспериментувати..... Почекавши хвилин
from flair.datasets import ColumnCorpus from flair.embeddings import WordEmbeddings, FlairEmbeddings, StackedEmbeddings from flair.models import SequenceTagger from flair.trainers import ModelTrainer # 1. get the corpus columns = {0: "text", 1: "pos", 2: "np", 3: "ner"} data_folder = '/content/drive/MyDrive/conll2003' corpus = ColumnCorpus(data_folder, columns, train_file='/content/drive/MyDrive/conll2003/train.txt', test_file='/content/drive/MyDrive/conll2003/test.txt') print(corpus) # 2. What label do we want to predict? label_type = 'ner' # 3. make the label dictionary from the corpus label_dict = corpus.make_label_dictionary(label_type=label_type) print(label_dict) # 4. initialize embedding stack with Flair and GloVe embedding_types = [ WordEmbeddings('glove'), FlairEmbeddings('news-forward'), FlairEmbeddings('news-backward'), ] embeddings = StackedEmbeddings(embeddings=embedding_types) # 5. initialize sequence tagger tagger = SequenceTagger(hidden_size=256, embeddings=embeddings, tag_dictionary=label_dict, tag_type=label_type, use_crf=True) # 6. initialize trainer trainer = ModelTrainer(tagger, corpus) # 7. start training trainer.train('resources/taggers/sota-ner-flair', learning_rate=0.1, mini_batch_size=32, max_epochs=5, embeddings_storage_mode="gpu")
Як на мене, результат досить непоганий, якщо ви все правильно зробили, F1 вийде більше 96%.
I am Yan <B-PER> Buteslkyy <I-PER> I work for New <B-ORG> Fire <I-ORG> Partners <I-ORG> company .
Заключне слово
Ну що ж, ми познайомились з одним з елементів пайплайну діалогової системи, який відповідає за розуміння природної мови. Вибрані приклади бібліотек я особисто використовував, тому можу гарантувати їхню надійність та ефективність. Кожна з них пройшла бойове хрещення в продакшені. Також сподіваюсь, що виконав побажання читачів в коментарях, які просили більше прикладів з реальної імплементації. Обіцяю продовжити розповідати про наступні елементи пайплайну діалогової системи. Сподіваюсь, що в кінці наша діалогова система зможе відповідати на фразу «Слава Україні!».
8 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів