ОБРАБОТКА ЕСТЕСТВЕННОГО ЯЗЫКА ПИТОН

ОБРАБОТКА ЕСТЕСТВЕННОГО ЯЗЫКА ПИТОН Edu.Vsu.Ru

Содержание
  1. Примеры практических задач SpaCy
  2. Анализ тональности текста
  3. Извлечение ключевых слов
  4. Автоматическая аннотация текстов
  5. Предварительная обработка и очистка текстовых данных
  6. Загрузка и подготовка данных
  7. Использование классификаторов машинного обучения для прогнозирования настроений
  8. Инструменты машинного обучения
  9. Приведение к нормальной форме
  10. Векторизация текста
  11. Создаем собственный анализатор тональности текстов
  12. Работа с векторными представлениями
  13. Встроенные векторы слов
  14. Понятие векторного представления слов
  15. Использование встроенных векторов слов в spaCy
  16. Пользовательские векторы
  17. Обучение собственных векторных представлений на корпусе текстов
  18. Применение пользовательских векторов в задачах классификации и кластеризации
  19. Удаление стоп-слов
  20. Python-библиотека NLTK
  21. Обучение классификатора
  22. Изменение конвейера spaCy для включения textcat
  23. Пишем цикл обучения textcat
  24. Оценка прогресса обучения модели
  25. Классифицируем обзоры
  26. Синтаксический анализ
  27. Дерево зависимостей
  28. Структура дерева зависимостей
  29. Использование дерева зависимостей для анализа отношений между словами
  30. Грамматические отношения
  31. Понятие синтаксических отношений (субъект, объект и др.)
  32. Примеры использования грамматических отношений для извлечения информации
  33. Токенизация
  34. Примеры
  35. Cortana
  36. Siri
  37. Gmail
  38. Dialogflow
  39. Как использовать spaCy для классификации текста
  40. Оценка модели на рецензиях с Кинопоиска
  41. Заключение

Примеры практических задач SpaCy

Анализ тональности текста

Анализ тональности текста является важным компонентом анализа настроений и мнений в текстовых данных. SpaCy, хотя и не является специализированной библиотекой для анализа тональности, может быть полезным инструментом для предварительной обработки и анализа текстов перед применением специализированных методов.

   import spacy
from textblob import TextBlob

nlp = spacy.load("en_core_web_sm")

text = "I love this product. It's amazing!"

doc = nlp(text)

# Используем TextBlob для анализа тональности
analysis = TextBlob(text)

# Оцениваем настроение текста
sentiment = analysis.sentiment.polarity

if sentiment > 0:
    sentiment_label = "positive"
elif sentiment < 0:
    sentiment_label = "negative"
else:
    sentiment_label = "neutral"

print(f"Sentiment: {sentiment_label}")
  
  

Извлечение ключевых слов

Извлечение ключевых слов из текста помогает сжать информацию и выделить самые важные аспекты содержания. SpaCy предоставляет возможность извлекать ключевые слова, используя частоту слов или их семантическое значение.

   import spacy

nlp = spacy.load("en_core_web_sm")

text = "Natural language processing is a field of study focused on making sense of text data."

doc = nlp(text)

# Извлекаем ключевые слова на основе частоты
keywords_freq = [token.text for token in doc if not token.is_stop and token.is_alpha]

# Извлекаем ключевые слова на основе веса встроенных векторов
keywords_semantic = [token.text for token in doc if not token.is_stop and token.vector_norm > 0]

print("Keywords based on frequency:", keywords_freq)
print("Keywords based on semantics:", keywords_semantic)
  
  

Автоматическая аннотация текстов

Автоматическая аннотация текстов позволяет автоматически добавлять дополнительные метаданные к текстам. Это может быть полезно, например, при создании информационных ресурсов, где необходимо предоставить пользователям дополнительные сведения о тексте.

   import spacy

nlp = spacy.load("en_core_web_sm")

text = "John is a data scientist working in a tech company."

doc = nlp(text)

# Добавляем аннотацию с информацией о профессии и компании
doc.ents = [(doc[3:5], "PERSON"), (doc[8:11], "ORG")]

# Выводим аннотированный текст
for ent in doc.ents:
    print(ent.text, "-", ent.label_)
  
  

SpaCy позволяет не только анализировать тексты, но и добавлять к ним дополнительные метаданные для обогащения контента. Это может быть полезно при создании разнообразных приложений, таких как информационные порталы, поисковые системы и многие другие.

Предварительная обработка и очистка текстовых данных

Любой рабочий процесс анализа данных начинается с их загрузки. Далее мы должны пропустить их через конвейер (pipeline) предобработки:

  • токенизировать текст

    – разбить текст на предложения, слова и другие единицы;
  • удалить стоп-слова
    ;
  • привести слова к нормальной форме
    ;
  • векторизовать тексты

    – сделать числовые представления текстов для их дальнейшей обработки классификатором.

Все эти шаги служат для уменьшения шума, присущего любому обычному тексту, и повышения точности результатов классификатора. Для решения указанных задач есть несколько отличных библиотек, например,
NLTK
,
TextBlob

и
spaCy
. Последнюю мы и будем применять в этом руководстве.

Прежде чем идти дальше, установите библиотеку spaCy и модель для английского языка:

   pip install spacy
python -m spacy download en_core_web_sm  

  

Использование spaCy для текстов на русском языке подробно описано в README-файле репозитория spacy-ru
.

Загрузка и подготовка данных

Далее мы считаем, что вы уже извлекли директорию, содержащую набор данных. Разобьем этап загрузки на конкретные шаги:

  1. Загрузим текст и метки из структуры файлов и каталогов.
  2. Перемешаем данные.
  3. Разделим данные на обучающий и тестовый наборы.
  4. Вернём два набора данных.

Этот процесс самодостаточен, поэтому его логично «упаковать» в отдельную функцию:

   def load_training_data(
    data_directory: str = "aclImdb/train",
    split: float = 0.8,
    limit: int = 0
) -> tuple:  

  

В сигнатуре функции мы используем аннотацию типов Python 3, чтобы было ясно, какие типы аргументов ожидает функция и какой тип она возвращает. Приведенные параметры позволяют определить каталог, в котором хранятся данные ( data_directory
), соотношение обучающих и тестовых данных ( split
) и количество отбираемых записей ( limit
). Далее нам нужно перебрать все файлы в наборе данных и загрузить их данные в список:

   # одиночными символами решетки здесь и далее помечен код,
# добавленный или изменившийся в сравнении с предыдущим кодом
import os                                                     #

def load_training_data(
    data_directory: str = "aclImdb/train",
    split: float = 0.8,
    limit: int = 0) -> tuple:
    # Загрузка данных из файлов
    reviews = []                                              #
    for label in ["pos", "neg"]:                              #
        labeled_directory = f"{data_directory}/{label}"       #
        for review in os.listdir(labeled_directory):          #
            if review.endswith(".txt"):                       #
                with open(f"{labeled_directory}/{review}") as f:
                    text = f.read()                           #
                    text = text.replace("<br />", "\n\n")     #
                    if text.strip():                          #
                        spacy_label = {                       #
                            "cats": {                         #
                                "pos": "pos" == label,        #
                                "neg": "neg" == label         #
                            }                                 #
                        }                                     #
                        reviews.append((text, spacy_label))   #  

  

Хотя это может показаться сложным, здесь мы просто создаем структуру каталогов данных, ищем и открываем текстовые файлы, а затем добавляем кортеж содержимого и словарь меток в список рецензий
reviews
. Оформление меток в виде словаря – это формат, используемый моделями spaCy во время обучения.

Поскольку на этом этапе мы открываем каждый обзор, здесь же полезно заменить HTML-теги
<br />

символами новой строки и использовать строковый метод
.strip ()

для удаления начальных и конечных пробелов.

В этом проекте мы не будем сразу удалять стоп-слова из обучающей выборки – это может изменить значение предложения или фразы и снизить предсказательную силу классификатора.

После загрузки файлов мы хотим их перетасовать, чтобы устранить любое влияние, обусловленное порядком загрузки обучающих данных:

   import os
import random                                  #

def load_training_data(
    data_directory: str = "aclImdb/train",
    split: float = 0.8,
    limit: int = 0
) -> tuple:
    # Загрузка данных из файлов
    reviews = []
    for label in ["pos", "neg"]:
        labeled_directory = f"{data_directory}/{label}"
        for review in os.listdir(labeled_directory):
            if review.endswith(".txt"):
                with open(f"{labeled_directory}/{review}") as f:
                    text = f.read()
                    text = text.replace("<br />", "\n\n")
                    if text.strip():
                        spacy_label = {
                            "cats": {
                                "pos": "pos" == label,
                                "neg": "neg" == label}
                        }
                        reviews.append((text, spacy_label))
    random.shuffle(reviews)                    #

    if limit:                                  #
        reviews = reviews[:limit]              #
    split = int(len(reviews) * split)          #
    return reviews[:split], reviews[split:]    #  

  

В добавленных строках кода мы перемешали записи из данных с помощью вызова
random.shuffle()
. Затем мы разбиваем и разделяем данные. Наконец, возвращаем два списка обзоров.

Выведем пример записи:

   load_training_data(
    data_directory = "aclImdb/train",
    split = 0.8,
    limit = 0)[0][0]  

  
   ('HOLLOW MAN is one of the better horror films of the past decade.
The sub-plot is original and the main plot is even better.
The special effects are brilliant and possibly the best I have ever
seen in a horror film. Kevin Bacon proves again that he can handle...',
 {'cats': {'pos': True, 'neg': False}})  

  

Создатели spaCy также выпустили пакет под названием thinc
( репозиторий GitHub
), который, помимо других функций, включает упрощенный доступ к большим наборам данных, в том числе датасет обзоров IMDB, используемый в этом проекте.

Использование классификаторов машинного обучения для прогнозирования настроений

Теперь текст преобразован в форму, понятную компьютеру, так что мы можем начать работу над его классификацией. Мы рассмотрим три темы, которые дадут общее представление о классификации текстовых данных в результате машинного обучения:

  1. Инструменты машинного обучения для задач классификации.
  2. Как происходит классификация.
  3. Как использовать spaCy для классификации текста.

Инструменты машинного обучения

В мире Python есть ряд инструментов для решения задач классификации. Вот некоторые из наиболее популярных:

Этот список не является исчерпывающим
, но это наиболее широко используемые фреймворки (библиотеки) машинного обучения, доступные для питонистов. Это мощные инструменты, на освоение и понимание которых уходит много времени.

К счастью, spaCy предоставляет довольно простой встроенный классификатор текста. Для начала важно понять общий рабочий процесс любого вида задач классификации:

  1. Разделяем данные на обучающую и тестовую выборки (наборы данных).
  2. Выбираем архитектуру модели.
  3. Используем обучающие данные для настройки параметров модели (этот процесс и называется обучением).
  4. Используем тестовые данные, чтобы оценить качество обучения модели.
  5. Используем обученную модель на новых, ранее не рассматривавшихся входных данных для создания прогнозов.

Cпециалисты по машинному обучению обычно разделяют набор данных на три составляющих:

  1. Данные для обучения (training).
  2. Данные для валидации (validation).
  3. Данные для теста (test).

Обучающий набор данных
, как следует из названия, используется для обучения модели. Валидационные данные
используются для настройки гиперпараметров модели и оценки модели непосредственно в процессе обучения. Гиперпараметры контролируют процесс обучения и структуру модели. Такими параметрами являются, например, скорость обучения и размер пакета данных (батчей). Набор гиперпараметров зависит от используемой модели. Тестовый набор данных
– включает данные, позволяющие судить о конечном качестве работы модели.

Фактически в рамках руководства мы ограничимся лишь валидацией модели в процессе ее обучения. Данные для обучения и валидации мы берем лишь из обучающей выборки. В качестве проверки модели мы ограничимся лишь единичным примером. Расширить тестирование не составит труда – тестовый набор данных включен в репозиторий и имеет ту же структуру, что и обучающие данные. В постскриптуме статьи рассмотрен необычный пример процедуры тестирования обученной модели на сторонних данных несколько иной природы.

Теперь, когда мы в общих чертах рассмотрели процесс классификации, пора применить его с помощью spaCy.

Приведение к нормальной форме

В процессе
нормализации

все формы слова приводятся к единому представлению. Например,
watched
,
watching

и
watches

после нормализации превращаются в
watch
. Есть два основных подхода к нормализации: стемминг и лемматизация.

В случае
стемминга

выделяется основа слова, дополнив которую можно получить слова-потомки. Такой метод сработает на приведенном примере. Однако это наивный подход – стемминг просто обрезает строку, отбрасывая окончание. Такой метод не обнаружит связь между
feel

и
felt
.

Лемматизация

стремится решить указанную проблему, используя структуру данных, в которой все формы слова связываются с его простейшей формой – леммой. Лемматизация обычно приносит больше пользы, чем стемминг, и потому является единственной стратегией нормализации, предлагаемой spaCy. В рамках NLP-конвейера лемматизация происходит автоматически. Лемма для каждого токена хранится в атрибуте
.lemma_
:

   lemmas = [
    f"Token: {token}, lemma: {token.lemma_}"
    for token in filtered_tokens
]

print(lemmas)  

  
   ['Token: \n, lemma: \n', 'Token: Dave, lemma: Dave',
'Token: watched, lemma: watch', 'Token: forest, lemma: forest',
'Token: burned, lemma: burn', 'Token: hill, lemma: hill',
'Token: ,, lemma: ,', 'Token: \n, lemma: \n',
'Token: miles, lemma: mile', 'Token: house, lemma: house',
'Token: ., lemma: .', 'Token: car, lemma: car',
'Token: \n, lemma: \n', 'Token: hastily, lemma: hastily',
'Token: packed, lemma: pack', 'Token: Marta, lemma: Marta', 
'Token: inside, lemma: inside', 'Token: trying, lemma: try',
'Token: round, lemma: round', 'Token: \n, lemma: \n', 
'Token: pets, lemma: pet', 'Token: ., lemma: .',
'Token: ", lemma: "', 'Token: ?, lemma: ?',
'Token: ", lemma: "', 'Token: wondered, lemma: wonder',
'Token: \n, lemma: \n', 'Token: continued, lemma: continue',
'Token: wait, lemma: wait', 'Token: Marta, lemma: Marta',
'Token: appear, lemma: appear', 'Token: pets, lemma: pet',
'Token: ., lemma: .', 'Token: \n, lemma: \n']  

  

Обратите внимание на символ подчеркивания в атрибуте .lemma_
. Это не опечатка, а результат соглашения по именованию в spaCy атрибутов, которые могут быть прочитаны человеком.

Следующим шагом является представление каждого токена способом, понятным машине. Этот процесс называется векторизацией.

Векторизация текста

Векторизация

– преобразование токена в числовой массив, который представляет его свойства. В контексте задачи вектор уникален для каждого токена. Векторные представления токенов используются для оценки сходства слов, классификации текстов и т. д. В spaCy токены векторизуются в виде плотных массивов, в которых для каждой позиции определены ненулевые значений. Это отличает используемый подход от ранних методов, в которых для тех же целей применялись разреженные массивы и большинство позиций были заполнены нулями.

Как и другие шаги, векторизация выполняется автоматически в результате вызова
nlp()
. Получим векторное представление одного из токенов:

   filtered_tokens[1].vector  

  
   array([ 1.8371646 ,  1.4529226 , -1.6147211 ,  0.678362  , -0.6594443 ,
        1.6417935 ,  0.5796405 ,  2.3021278 , -0.13260496,  0.5750932 ,
        1.5654886 , -0.6938864 , -0.59607106, -1.5377437 ,  1.9425622 ,
       -2.4552505 ,  1.2321601 ,  1.0434952 , -1.5102385 , -0.5787632 ,
        0.12055647,  3.6501784 ,  2.6160972 , -0.5710199 , -1.5221789 ,
        0.00629176,  0.22760668, -1.922073  , -1.6252862 , -4.226225  ,
       -3.495663  , -3.312053  ,  0.81387717, -0.00677544, -0.11603224,
        1.4620426 ,  3.0751472 ,  0.35958546, -0.22527039, -2.743926  ,
        1.269633  ,  4.606786  ,  0.34034157, -2.1272311 ,  1.2619178 ,
       -4.209798  ,  5.452852  ,  1.6940253 , -2.5972986 ,  0.95049495,
       -1.910578  , -2.374927  , -1.4227567 , -2.2528825 , -1.799806  ,
        1.607501  ,  2.9914255 ,  2.8065152 , -1.2510269 , -0.54964066,
       -0.49980402, -1.3882618 , -0.470479  , -2.9670253 ,  1.7884955 ,
        4.5282774 , -1.2602427 , -0.14885521,  1.0419178 , -0.08892632,
       -1.138275  ,  2.242618  ,  1.5077229 , -1.5030195 ,  2.528098  ,
       -1.6761329 ,  0.16694719,  2.123961  ,  0.02546412,  0.38754445,
        0.8911977 , -0.07678384, -2.0690763 , -1.1211847 ,  1.4821006 ,
        1.1989193 ,  2.1933236 ,  0.5296372 ,  3.0646474 , -1.7223308 ,
       -1.3634219 , -0.47471118, -1.7648507 ,  3.565178  , -2.394205  ,
       -1.3800384 ], dtype=float32)  

  

Здесь мы используем атрибут .vector
для второго токена в списке filter_tokens
. В этом наборе это слово Dave
.

Если для атрибута .vector
вы получили другой результат, не беспокойтесь. Это может быть связано с тем, что используется другая версия модели en_core_web_sm
или самой библиотеки spaCy
.

Создаем собственный анализатор тональности текстов

В качестве набора данных, на которых будет происходить обучение и проверка модели мы будем использовать набор данных
Large Movie Review
, собранный
Эндрю Маасом
.

Вставка от переводчика
. Использование базы данных IMDB является стандартным источником для сентимент-анализа. В рецензиях к фильмам сами пользователи уже соотнесли рецензию и оценку фильма – как бы разметили данные, сопоставив блок текста и его категорию.

Предлагаемый в пособии набор данных хранится в виде сжатого tar-архива, который можно извлечь на диск непосредственно из Python. Датасеты обычно хранятся отдельно от блокнотов Jupyter (чтобы не выгружать их на GitHub), поэтому для дальнейшей работы полезно сменить текущую рабочую директорию:

   import os
import tarfile

# не забудьте изменить путь к директории с архивом
# на тот, что используется в вашей системе
os.chdir(os.path.relpath('../../../Datasets/'))

fname = 'aclImdb_v1.tar.gz'
with tarfile.open(fname, "r:gz") as tar:
    tar.extractall()
    tar.close()  

  

Туда же впоследствии мы сохраним обученную модель классификации данных.

Работа с векторными представлениями

Встроенные векторы слов

Понятие векторного представления слов

Векторные представления слов — это числовые векторы, представляющие слова в многомерном пространстве таким образом, что семантически близкие слова имеют близкие векторы. Это понятие основано на гипотезе о дистрибутивности, согласно которой слова, используемые в похожих контекстах, имеют схожие значения.

Использование встроенных векторов слов в spaCy

Одной из мощных особенностей spaCy является наличие встроенных векторов слов, которые могут быть использованы для анализа семантической близости и сходства между словами. Эти векторы обучены на больших объемах текста и позволяют сравнивать слова на основе их семантического значения.

   import spacy

nlp = spacy.load("en_core_web_sm")

# Получаем векторное представление слова "cat"
vector_cat = nlp("cat").vector

# Получаем векторное представление слова "dog"
vector_dog = nlp("dog").vector

# Вычисляем косинусное расстояние между векторами
similarity = vector_cat.dot(vector_dog) / (vector_cat.norm() * vector_dog.norm())

print("Similarity between 'cat' and 'dog':", similarity)
  
  

Пользовательские векторы

Обучение собственных векторных представлений на корпусе текстов

В некоторых случаях может потребоваться обучить собственные векторные представления слов на специфическом корпусе текстов. Это особенно полезно, если вы работаете с узкоспециализированной терминологией или имеете доступ к большому объему текстов данных.

   import spacy
from gensim.models import Word2Vec

# Загружаем языковую модель
nlp = spacy.load("en_core_web_sm")

# Подготавливаем текстовый корпус
corpus = ["I like cats.", "Dogs are friendly.", "Cats and dogs are pets."]

# Токенизируем и лемматизируем текст
processed_corpus = []
for doc in nlp.pipe(corpus):
    processed_corpus.append([token.lemma_ for token in doc])

# Обучаем модель Word2Vec
model = Word2Vec(processed_corpus, vector_size=100, window=5, min_count=1, sg=0)

# Сохраняем модель
model.save("custom_word_vectors.model")
  
  

Применение пользовательских векторов в задачах классификации и кластеризации

Пользовательские векторные представления слов могут быть использованы в различных задачах машинного обучения, таких как классификация и кластеризация. Например, вы можете использовать эти векторы для обучения модели классификации тональности текста или для кластеризации похожих документов.

   from sklearn.svm import SVC
from sklearn.metrics import accuracy_score

# Загружаем модель Word2Vec
custom_model = Word2Vec.load("custom_word_vectors.model")

# Получаем векторное представление слова "cat"
vector_cat = custom_model.wv["cat"]

# Получаем векторное представление слова "dog"
vector_dog = custom_model.wv["dog"]

# Подготавливаем данные для классификации
X = [vector_cat, vector_dog]
y = ["animal", "animal"]

# Обучаем модель классификации
classifier = SVC()
classifier.fit(X, y)

# Тестируем модель
test_vector = custom_model.wv["dog"]
predicted_label = classifier.predict([test_vector])[0]

print("Predicted label for 'dog':", predicted_label)  
  

Векторные представления слов открывают двери к множеству интересных задач. Они позволяют не только анализировать близость слов, но и использовать эту информацию для решения более сложных задач, таких как классификация и кластеризация. Ваша способность создавать и использовать собственные векторы дает вам бескрайний потенциал в области анализа текста.

Удаление стоп-слов

Стоп-слова

– это слова, которые могут иметь важное значение в человеческом общении, но не имеют смысла для машин. Библиотека spaCy поставляется со списком стоп-слов по умолчанию (его можно настроить). Проведем фильтрацию полученного списка:

   filtered_tokens = [token for token in doc if not token.is_stop]
print(filtered_tokens)  

  
   [
, Dave, watched, forest, burned, hill, ,, 
, miles, house, ., car, 
, hastily, packed, Marta, inside, trying, round, 
, pets, ., ", ?, ", wondered, 
, continued, wait, Marta, appear, pets, ., 
]  

  

Одной строкой Python-кода мы отфильтровали стоп-слова из токенизированного текста с помощью атрибута токенов .is_stop
. После удаления стоп-слов список стал короче, исчезли местоимения и служебные слова: артикли, союзы, предлоги и послелоги.

Python-библиотека NLTK

NLTK (Natural Language Toolkit) – ведущая платформа для создания NLP-программ на Python. У нее есть легкие в использовании интерфейсы для многих языковых корпусов
, а также библиотеки для обработки текстов для классификации, токенизации, стемминга
, разметки
, фильтрации и семантических рассуждений
. Ну и еще это бесплатный опенсорсный проект, который развивается с помощью коммьюнити.

Мы будем использовать этот инструмент, чтобы показать основы NLP. Для всех последующих примеров я предполагаю, что NLTK уже импортирован; сделать это можно командой import nltk

Обучение классификатора

Конвейер spaCy позволяет создать и обучить
сверточную нейронную сеть

(CNN) для классификации текстовых данных. Здесь этот подход используется для сентимент-анализа, но ничто не мешает распространить его и на другие задачи классификации текстов.

В этой части проекта мы выполним три шага:

  1. Измененим базовый конвейер spaCy для включения компонента
    textcat
    .
  2. Создадим цикл для обучения компонента
    textcat
    .
  3. Научимся оценивать прогресс обучения модели после заданного количества циклов обучения.

Изменение конвейера spaCy для включения textcat

Загрузим тот же конвейер, что и в примерах в начале руководства, далее добавим компонент
textcat
. После этого укажем
textcat

метки, которые используются в данных:
pos

для положительных отзывов и
neg

для негативных:

   import os
import random
import spacy

def train_model(
    training_data: list,
    test_data: list,
    iterations: int = 20
) -> None:
    # Строим конвейер
    nlp = spacy.load("en_core_web_sm")
    if "textcat" not in nlp.pipe_names:
        textcat = nlp.create_pipe(
            "textcat", config={"architecture": "simple_cnn"}
        )
        nlp.add_pipe(textcat, last=True)  

  

Если вы уже видели
пример textcat

из документации spaCy, то этот код будет вам знаком. Сначала мы загружаем встроенный конвейер
en_core_web_sm
, затем проверяем атрибут
.pipe_names
, чтобы узнать, доступен ли компонент
textcat
. Если это не так, создаем компонент с помощью метода
.create_pipe()
, передаем словарь с конфигурацией. Соответствующие инструкции описаны в
документации TextCategorizer
. Наконец, с помощью
.add_pipe()

добавляем компонент в конвейер, последний аргумент указывает, что этот компонент должен быть добавлен в конец конвейера.

Теперь нужно обработать случай, когда компонент
textcat

уже доступен. Добавляем метки:

   import os
import random
import spacy

def train_model(
    training_data: list,
    test_data: list,
    iterations: int = 20
) -> None:
    # Строим конвейер
    nlp = spacy.load("en_core_web_sm")
    if "textcat" not in nlp.pipe_names:
        textcat = nlp.create_pipe(
            "textcat", config={"architecture": "simple_cnn"}
        )
        nlp.add_pipe(textcat, last=True)
    else:                                   #
        textcat = nlp.get_pipe("textcat")   #

    textcat.add_label("pos")                #
    textcat.add_label("neg")                #  

  

Пишем цикл обучения textcat

Чтобы начать цикл обучения, настраиваем конвейер на обучение компонента
textcat
, генерируем для него пакеты данных с помощью функций из пакета
spacy.util


minibatch()

и
compounding()
. Под пакетом данных, батчем (англ. batch) понимается просто та небольшая часть данных, которая участвует в обучении. Пакетная обработка данных позволяет сократить объем памяти, используемый во время обучения и быстрее обновлять гиперпараметры.

Динамический размер батча (compounding) – относительно новая методика методов машинного обучения, которая должна приводить к ускорению процесса обучения. О ней можно прочитать в советах по обучению spaCy
.

Реализуем описанный цикл обучения, добавив необходимые строки:

   import os
import random
import spacy
from spacy.util import minibatch, compounding  #

def train_model(
    training_data: list,
    test_data: list,
    iterations: int = 20
) -> None:
    # Строим конвейер
    nlp = spacy.load("en_core_web_sm")
    if "textcat" not in nlp.pipe_names:
        textcat = nlp.create_pipe(
            "textcat", config={"architecture": "simple_cnn"}
        )
        nlp.add_pipe(textcat, last=True)
    else:
        textcat = nlp.get_pipe("textcat")

    textcat.add_label("pos")
    textcat.add_label("neg")

    # Обучаем только textcat
    training_excluded_pipes = [                               #
        pipe for pipe in nlp.pipe_names if pipe != "textcat"  #
    ]                                                         #
    with nlp.disable_pipes(training_excluded_pipes):          #
        optimizer = nlp.begin_training()                      #
        # Итерация обучения                           
        print("Начинаем обучение")                            #
        batch_sizes = compounding(                            #
            4.0, 32.0, 1.001                                  #
        )  # Генератор бесконечной последовательности входных чисел  

  

В последних строчках функции создаем список компонентов в конвейере, которые не являются компонентами
textcat
. Далее используем диспетчер контекста
nlp.disable()
, чтобы отключить эти компоненты для всего кода в области действия диспетчера контекста.

Далее мы вызываем функцию
nlp.begin_training()
, которая возвращает начальную функцию оптимизатора. Это то, что
nlp.update()

будет впоследствии использовать для обновления весов базовой модели. Затем используем функцию
compounding()

для создания генератора, дающего последовательность значений
batch_sizes
, которые в дальнейшем будут приниматься функцией
minibatch()
.

Теперь добавим обучение на батчах:

   import os
import random
import spacy
from spacy.util import minibatch, compounding  #

def train_model(
    training_data: list,
    test_data: list,
    iterations: int = 20
) -> None:
    # Строим конвейер
    nlp = spacy.load("en_core_web_sm")
    if "textcat" not in nlp.pipe_names:
        textcat = nlp.create_pipe(
            "textcat", config={"architecture": "simple_cnn"}
        )
        nlp.add_pipe(textcat, last=True)
    else:
        textcat = nlp.get_pipe("textcat")

    textcat.add_label("pos")
    textcat.add_label("neg")

    # Обучаем только textcat
    training_excluded_pipes = [
        pipe for pipe in nlp.pipe_names if pipe != "textcat"
    ]
    with nlp.disable_pipes(training_excluded_pipes):
        optimizer = nlp.begin_training()
        print("Начинаем обучение")
        batch_sizes = compounding(
            4.0, 32.0, 1.001
        )  # Генератор бесконечной последовательности входных чисел
        for i in range(iterations):                           #
            loss = {}                                         #
            random.shuffle(training_data)                     #
            batches = minibatch(training_data, size=batch_sizes)
            for batch in batches:                             #
                text, labels = zip(*batch)                    #
                nlp.update(                                   #
                    text,                                     #
                    labels,                                   #
                    drop=0.2,                                 #  
                    sgd=optimizer,                            #
                    losses=loss                               #
                )                                             #  

  

Теперь для каждой итерации, указанной в сигнатуре
train_model()
, мы создаем пустой словарь с именем
loss
, который будет обновляться и использоваться функцией
nlp.update()
. Перетасовываем обучающие данные и разделяем их на пакеты разного размера с помощью функции
minibatch()
.

Для каждого пакета отделяем текст ( text
) от меток ( labels
) и передаем их в оптимизатор
nlp.update()
. Это фактически запускает обучение.

Параметр
dropout

сообщает
nlp.update()
, какую часть обучающих данных в этом пакете нужно пропустить. Это делается для того, чтобы модели было сложнее переобучиться – запомнить обучающие данные без создания обобщающей модели.

Оценка прогресса обучения модели

Поскольку мы будем выполнять множество оценок на большом количестве вычислений, имеет смысл написать отдельную функцию
evaluate_model()
. В этой функции мы будем классифицировать тексты из валидационного набора данных на недообученной модели и сравнивать результаты модели с метками исходных данных.

Используя эту информацию, мы вычислим следующие метрики:

  • Истинно положительные

    (true positives,
    ) – число отзывов, которые модель правильно предсказала как положительные.
  • Ложноположительные

    (false positives,
    ) – число отзывов, которые модель неверно предсказала как положительные, хотя на самом деле они были негативными.
  • Истинно отрицательные

    (true negatives,
    ) – число отзывов, которые модель правильно предсказала как негативные.
  • Ложноотрицательные

    (false negatives,
    ) – число отзывов, которые модель неверно предсказала как негативные, хотя на самом деле они были положительными.

Поскольку наша модель для каждой метки возвращает оценку от 0 до 1, мы определяем положительный или отрицательный результат на основе этой оценки. На основе четырех описанных статистических данных мы вычисляем две метрики: точность и полноту. Эти метрики являются показателями эффективности модели классификации:

  • Точность

    (precision) – отношение истинно положительных результатов ко всем элементам, отмеченным моделью как положительные (истинные и ложные срабатывания). Точность 1.0 означает, что каждый отзыв, отмеченный нашей моделью как положительный, действительно относится к положительному классу.
  • Полнота

    (recall) – это отношение истинно положительных отзывов ко всем фактическим положительным отзывам, то есть количество истинно положительных отзывов, деленных на суммарное количество истинно положительных и ложноотрицательных отзывов.

Ещё более популярной метрикой является F1-мера – среднее гармоническое точности и полноты. Максимизация F1-меры приводит к одновременной максимизации этих двух критериев:

В evaluate_model()
необходимо передать токенизатор, textcat
и тестовый набор данных.

   def evaluate_model(tokenizer, textcat, test_data: list) -> dict:
    reviews, labels = zip(*test_data)
    reviews = (tokenizer(review) for review in reviews)
    # Указываем TP как малое число, чтобы в знаменателе
    # не оказался 0
    TP, FP, TN, FN = 1e-8, 0, 0, 0
    for i, review in enumerate(textcat.pipe(reviews)):
        true_label = labels[i]['cats']
        score_pos = review.cats['pos'] 
        if true_label['pos']:
            if score_pos >= 0.5:
                TP += 1
            else:
                FN += 1
        else:
            if score_pos >= 0.5:
                FP += 1
            else:
                TN += 1    
    precision = TP / (TP + FP)
    recall = TP / (TP + FN)
    f_score = 2 * precision * recall / (precision + recall)
    return {"precision": precision, "recall": recall, "f-score": f_score}  

  

В этой функции мы разделяем обзоры и их метки, затем используем выражение-генератор для токенизации каждого из обзоров, подготавливая их для передачи в
textcat
. Выражение-генератор позволяет перебирать токенизированные обзоры, не сохраняя каждый из них в памяти.

Затем мы используем
score

и
true_label

для определения ложных и истинных срабатываний модели, которые далее подставляем для расчета точности, полноты и F-меры.

Вызовем
evaluate_model()

из описанной ранее функции
train_model()
:

   import os
import random
import spacy
from spacy.util import minibatch, compounding

def train_model(
    training_data: list,
    test_data: list,
    iterations: int = 20) -> None:
    # Строим конвейер
    nlp = spacy.load("en_core_web_sm")
    if "textcat" not in nlp.pipe_names:
        textcat = nlp.create_pipe(
            "textcat", config={"architecture": "simple_cnn"}
        )
        nlp.add_pipe(textcat, last=True)
    else:
        textcat = nlp.get_pipe("textcat")

    textcat.add_label("pos")
    textcat.add_label("neg")

    # Обучаем только textcat
    training_excluded_pipes = [
        pipe for pipe in nlp.pipe_names if pipe != "textcat"
    ]
    with nlp.disable_pipes(training_excluded_pipes):
        optimizer = nlp.begin_training()
        # Training loop
        print("Начинаем обучение")
        print("Loss\t\tPrec.\tRec.\tF-score")          #
        batch_sizes = compounding(
            4.0, 32.0, 1.001
        )  # Генератор бесконечной последовательности входных чисел
        for i in range(iterations):
            loss = {}
            random.shuffle(training_data)
            batches = minibatch(training_data, size=batch_sizes)
            for batch in batches:
                text, labels = zip(*batch)
                nlp.update(
                    text,
                    labels,
                    drop=0.2,
                    sgd=optimizer,
                    losses=loss
                )
            with textcat.model.use_params(optimizer.averages):
                evaluation_results = evaluate_model(   #
                    tokenizer=nlp.tokenizer,           #
                    textcat=textcat,                   #
                    test_data=test_data                #
                )                                      #
                print(f"{loss['textcat']:9.6f}\t\
{evaluation_results['precision']:.3f}\t\
{evaluation_results['recall']:.3f}\t\
{evaluation_results['f-score']:.3f}")
                
    # Сохраняем модель                                 #
    with nlp.use_params(optimizer.averages):           #
        nlp.to_disk("model_artifacts")                 #  

  

Здесь мы добавили несколько вызовов
print()
, чтобы помочь организовать вывод от функции
evaluate_model()
, которую вызываем в контекстном менеджере
.use_param ()
, чтобы оценить модель в ее текущем состоянии.

После завершения процесса обучения сохраняем только что обученную модель в каталоге с именем
model_artifacts
:

Итак, мы получили функцию, которая обучает модель, отображает оценку обучения и позволяет сохранить результат. Произведем ее обучение:

   train, test = load_training_data(limit=5000)
train_model(train, test, iterations=10)  

  
   Начинаем обучение
Loss		Prec.	Rec.	F-score
13.758302	0.809	0.776	0.792
 1.080611	0.827	0.784	0.805
 0.264118	0.833	0.776	0.804
...
 0.005302	0.833	0.776	0.804  

  

Время обучения зависит от используемой системы. Его можно сократить, уменьшив размер выборки ( limit
), однако в этом случае есть риск получить менее точную модель.

По мере обучения модели вы будете видеть, как меняются показатели потерь, точности, полноты и F-меры для каждой итерации обучения. Значение функции потерь стремительно уменьшается с каждой итерацией. Остальные параметры также должны меняться, но не так значительно: обычно они растут на самых первых итерациях, а после этого держатся примерно на одном уровне.

Характерное изменение значения функции потерь от числа итерации
Характерное изменение значения функции потерь от числа итерации

Классифицируем обзоры

Теперь у нас есть обученная модель, пора протестировать ее на реальных обзорах – насколько успешно она справится с оценкой их эмоциональной окраски. Для целей этого проекта мы оценим лишь один обзор, вместо которого вы можете подставить любые иные строковые значения.

   TEST_REVIEW = """
Transcendently beautiful in moments outside the office, it seems almost
sitcom-like in those scenes. When Toni Colette walks out and ponders
life silently, it's gorgeous.<br /><br />The movie doesn't seem to decide
whether it's slapstick, farce, magical realism, or drama, but the best of it
doesn't matter. (The worst is sort of tedious - like Office Space with less humor.)
"""  

  

Передадим текст обзора модели, чтобы сгенерировать прогноз и отобразить его пользователю:

   def test_model(input_data: str):
    # Загружаем сохраненную модель
    loaded_model = spacy.load("model_artifacts")
    parsed_text = loaded_model(input_data)
    # Определяем возвращаемое предсказание
    if parsed_text.cats["pos"] > parsed_text.cats["neg"]:
        prediction = "Положительный отзыв"
        score = parsed_text.cats["pos"]
    else:
        prediction = "Негативный отзыв"
        score = parsed_text.cats["neg"]
    print(f"Текст обзора: {input_data}\n\
Предсказание: {prediction}\n\
Score: {score:.3f}")  

  

В этом коде мы передаем входные данные в загруженную модель и генерируем предсказание в атрибуте cats
переменной parsed_text
. Затем проверяем оценки каждого настроения и сохраняем метки с более высоким прогнозом. Проверим, что модель корректно отрабатывает на отдельном примере.

   test_model(input_data=TEST_REVIEW)  

  
   Текст обзора: 
Transcendently beautiful in moments outside the office, it seems almost
sitcom-like in those scenes. When Toni Colette walks out and ponders
life silently, it's gorgeous.<br /><br />The movie doesn't seem to decide
whether it's slapstick, farce, magical realism, or drama, but the best of it
doesn't matter. (The worst is sort of tedious - like Office Space with less humor.)

Предсказание: Положительный отзыв
Score: 0.612  

  

Отзыв действительно несет положительную оценку. Параметр
Score

служит характеристикой уверенности модели. Проверьте поведение функции
test_model()

на других строковых значениях.

Итак, мы создали ряд независимых функций
load_data()
,
train_model()
,
evaluate_model()

и
test_model()
, которые, вместе взятые, будут загружать данные и обучать, оценивать, сохранять и тестировать классификатор анализа эмоциональной окраски текста в Python. Чтобы объединить их вместе в одном файле достаточно, использовать стандартную инструкцию
if __name__ == "__main__"
:

   if __name__ == "__main__":
    train, test = load_training_data(limit=2500)
    train_model(train, test)
    print("Testing model")
    test_model()  

  

Синтаксический анализ

Дерево зависимостей

Структура дерева зависимостей

Синтаксический анализ, также известный как анализ зависимостей, является важной частью обработки естественного языка. Он фокусируется на выявлении синтаксических отношений между словами в предложении. Для визуализации этих отношений используется дерево зависимостей, которое является графическим представлением структуры предложения.

Использование дерева зависимостей для анализа отношений между словами

Дерево зависимостей позволяет нам легко увидеть, какие слова являются главными, а какие зависимыми, а также какие синтаксические отношения связывают их. В spaCy можно получить дерево зависимостей для предложения с помощью метода .print_tree()
.

   import spacy

nlp = spacy.load("en_core_web_sm")

text = "The cat chased the mouse."

doc = nlp(text)

# Выводим дерево зависимостей
for token in doc:
    print(token.text, token.dep_, token.head.text)
  
  

Грамматические отношения

Понятие синтаксических отношений (субъект, объект и др.)

Синтаксические отношения определяют, как слова связаны между собой в предложении. Некоторые из ключевых синтаксических отношений включают субъект, объект, прямое дополнение, косвенное дополнение и т.д. Эти отношения помогают нам понимать семантическую структуру предложения.

Примеры использования грамматических отношений для извлечения информации

Грамматические отношения могут быть использованы для извлечения семантической информации из текста. Рассмотрим пример использования грамматических отношений для определения семантической роли слова в предложении.

   import spacy

nlp = spacy.load("en_core_web_sm")

text = "The cat chased the mouse."

doc = nlp(text)

# Извлекаем грамматические отношения и семантические роли
for token in doc:
    if token.dep_ == "nsubj":
        print(f"Subject: {token.text}")
    elif token.dep_ == "dobj":
        print(f"Direct Object: {token.text}")
    elif token.dep_ == "prep":
        print(f"Preposition: {token.text}")
  
  

Синтаксический анализ дает нам обширный инсайт в то, как слова связаны между собой в предложении, и как эти отношения могут повлиять на их значения. Помимо анализа грамматической структуры, дерево зависимостей может быть использовано для выявления более сложных синтаксических конструкций и семантических связей.

Токенизация

Токенизация

– это процесс разбиения текста на более мелкие части. В библиотеку spaCy уже встроен конвейер (pipeline), который начинает свою работу по обработке текста с токенизации. В этом руководстве мы разделим текст на отдельные слова. Загрузим пример и проведем разбиение:

   import spacy
nlp = spacy.load("en_core_web_sm")

text = """
Dave watched as the forest burned up on the hill,
only a few miles from his house. The car had
been hastily packed and Marta was inside trying to round
up the last of the pets. "Where could she be?" he wondered
as he continued to wait for Marta to appear with the pets.
"""

doc = nlp(text)
token_list = [token for token in doc]

print(token_list)  

  
   [
, Dave, watched, as, the, forest, burned, up, on, the, hill, ,, 
, only, a, few, miles, from, his, house, ., The, car, had, 
, been, hastily, packed, and, Marta, was, inside, trying, to, round, 
, up, the, last, of, the, pets, ., ", Where, could, she, be, ?, ", he, wondered, 
, as, he, continued, to, wait, for, Marta, to, appear, with, the, pets, ., 
]  

  

Вы могли заметить, что захваченные токены включают знаки препинания и другие строки, не относящиеся к словам. Это нормальное поведение в случае использований конвейера по умолчанию.

Примеры

Cortana

ОБРАБОТКА ЕСТЕСТВЕННОГО ЯЗЫКА ПИТОН

В Windows есть виртуальный помощник Cortana, который распознает речь. С помощью Cortana можно создавать напоминания, открывать приложения, отправлять письма, играть в игры, узнавать погоду и т.д.

Siri

ОБРАБОТКА ЕСТЕСТВЕННОГО ЯЗЫКА ПИТОН

Siri это помощник для ОС от Apple: iOS, watchOS, macOS, HomePod и tvOS. Множество функций также работает через голосовое управление: позвонить/написать кому-либо, отправить письмо, установить таймер, сделать фото и т.д.

Gmail

Известный почтовый сервис умеет определять спам, чтобы он не попадал во входящие вашего почтового ящика.

Dialogflow

ОБРАБОТКА ЕСТЕСТВЕННОГО ЯЗЫКА ПИТОН

Платформа от Google, которая позволяет создавать NLP-ботов. Например, можно сделать бота для заказа пиццы, которому не нужен старомодный IVR, чтобы принять ваш заказ
.


Как использовать spaCy для классификации текста

Мы уже знаем, что spaCy берет на свои плечи заботы о предварительной обработке текста с помощью конструктора
nlp()
. Конвейер по умолчанию определен в файле
JSON
, связанном с уже существующей моделью ( en_core_web_sm

в этом руководстве) или
моделью, созданной пользвателем
.

Один из встроенных компонентов конвейера называется
textcat

(сокр.
TextCategorizer
), который позволяет назначать текстовым данным категории и использовать их в качестве обучающих данных нейронной сети. Чтобы с помощью этого инструмента обучить модель, необходимо выполнить следующие действия:

  1. Добавить компонент
    textcat

    в существующий конвейер.
  2. Добавить в компонент
    textcat

    валидные метки (имена категорий).
  3. Загрузить, перемешать и разделить на части данные, на которые проходит обучение.
  4. Обучить модель, оценивая каждую итерацию обучения.
  5. Использовать обученную модель, чтобы предсказать тональность настроений на текстах, не входивших в обучающую выборку.
  6. При желании: сохранить обученную модель.

Реализацию этих шагов можно посмотреть в примерах документации spaCy
. Это основной способ классификации текста в spaCy, поэтому код проекта во многом похож на эти примеры.

Оценка модели на рецензиях с Кинопоиска

Напоследок проверим модель на аналогичных отзывах на русском языке. Для этого можно было бы использовать русскоязычную модель spaCy (см. примечание выше) и заново обучить модель на приведенных далее примерах, но ради интереса мы воспользуемся моделью, уже обученной на рецензиях IMDB. Для этого протестируем модель на датасете из 3000 записей, собранных с Кинопоиска
Денисом Кудрявцевым
. Выборка сбалансирована: содержится примерно по одной тысяче положительных, негативных и нейтральных отзывов (в датасете IMDB присутствуют только положительные и негативные отзывы). Для удобства работы мы преобразовали набор текстовых файлов в
единый csv-файл

и перевели колонку рецензий с помощью машинного перевода.

   import pandas as pd
import sklearn.metrics

df = pd.read_csv("kinopoisk.zip", index_col=0)
df.sample(frac=1)[:5]  

  

Адаптируем функцию test_model
для обновленной задачи.

   def test_model(input_data):
    loaded_model = spacy.load("model_artifacts")
    parsed_text = loaded_model(input_data)
    if parsed_text.cats["pos"] > parsed_text.cats["neg"]:
        prediction = "good"
        score = parsed_text.cats["pos"]
    else:
        prediction = "bad"
        score = parsed_text.cats["neg"]
    return prediction

pred = df.translation.apply(test_model)
df['pred'] = pred
yy = df.iloc[:1999]
y_true = yy['type']
y_pred = yy['pred']

y_true[y_true == 'good'] = 1
y_true[y_true == 'bad'] = 0
y_pred[y_pred == 'good'] = 1
y_pred[y_pred == 'bad'] = 0

accuracy = sklearn.metrics.accuracy_score(y_true.astype(int), y_pred.astype(int))
f_score = sklearn.metrics.f1_score(y_true.astype(int), y_pred.astype(int))

print(f"Метрика accuracy составила: {accuracy:.3f}.")
print(f"F1-мера модели равна {f_score:.3f}.")  

  
   Метрика accuracy составила: 0.693.
F1-мера модели равна 0.735.  

  

Несмотря на то что часть смыслового содержания скрадывается в результате машинного перевода с русского на английский, модель всё равно достаточно хорошо определяет тональность текстов.

   >>> df[df['type'] == 'neutral']['pred'].value_counts()
good    559
bad     441
Name: pred, dtype: int64  

  

Для нейтральных записей мы действительно получили один порядок числа положительных и негативных предсказаний – модель сомневается в разделении, ведь в ней не происходило обучения на нейтральных записях.

Если вам понравился этот материал, не забудьте поставить лайк. На сайте есть ряд других пошаговых туториалов по тематике
Data Science
, например:

  • Генеративно-состязательная нейросеть: ваша первая GAN-модель на PyTorch

  • Пишем нейросеть на Python с нуля
  • Ещё один наш текст о библиотеке spaCy

А если мозг уже достаточно напрягся, есть несложный тест на знание того, что умеют нейросети
.

Заключение

В этой статье были разобраны основы NLP для текста, а именно:

  • NLP позволяет применять алгоритмы машинного обучения для текста и речи;
  • NLTK (Natural Language Toolkit) – ведущая платформа для создания NLP-программ на Python;
  • токенизация по предложениям – это процесс разделения письменного языка на предложения-компоненты;
  • токенизация по словам – это процесс разделения предложений на слова-компоненты;
  • лемматизация и стемминг преследуют цель привести все встречающиеся словоформы к одной, нормальной словарной форме;
  • стоп-слова – это слова, которые выкидываются из текста до/после обработки текста;
  • регулярное выражение (регулярка, regexp, regex) – это последовательность символов, которая определяет шаблон поиска;
  • мешок слов – это популярная и простая техника извлечения признаков, используемая при работе с текстом. Она описывает вхождения каждого слова в текст.

Отлично! Теперь, зная основы выделения признаков, вы можете использовать признаки как входные данные для алгоритмов машинного обучения.

Если вы хотите увидеть все описанные концепции в одном большом примере, то вам сюда
.

Оцените статью