12.3. Преобразование текстовых данных и работа с ними в Python

В этом параграфе мы будем работать с корпусом произведений Николая Гоголя, Максима Горького, Льва Толстого, Фёдора Достоевского, Антона Чехова и Ивана Тургенева. На примере корпуса рассмотрим способы предобработки текстов, анализа тональности и извлечение именованных сущностей.

Для начала работы с текстовыми данными мы будем использовать следующие пакеты: NLTK, spaCy, pymorphy2 и Dostoevsky.

Простая предобработка текстовых данных

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

import pandas as pd
author_data = pd.read_csv('author_data.csv')
print(author_data.head())
                                                text      author
0               Эх, господа, как вы можете тут жить!     Chekhov
1                        — Ты мне пустого не говори.     Tolstoy
2  Такая поспешность такого надутого собою челове...  Dostoevsky
3  И самое лицо дома было веселое, стекла окон бл...       Gorky
4  Не говорите, дядечка, лишнего..    Любовь Андр...     Chekhov

Попробуем сделать простую предобработку. Для удаления знаков препинания мы можем использовать метод replace(), чтобы с помощью регулярных выражений заменить знаки препинания на пробелы. Подходящее регулярное выражение выглядит так: r'[^\w\s]'. В нем мы просим заменить все, что не относится к буквам (\w) или к пробелам (\s) в тексте, на пробел (' '). Затем, с помощью другого регулярного выражения, мы можем удалить все двойные пробелы. Наконец, мы приведём все буквы к нижнему регистру.

author_data['text_clean'] = author_data['text'].replace(r'[^\w\s]',' ',regex=True).replace(r'\s+',' ',regex=True).str.lower()
author_data['text_clean'].head()
0                   эх господа как вы можете тут жить 
1                            ты мне пустого не говори 
2    такая поспешность такого надутого собою челове...
3    и самое лицо дома было веселое стекла окон бле...
4        не говорите дядечка лишнего любовь андреевна 
Name: text_clean, dtype: object

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

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

import spacy
from spacy import load
from spacy.lang.ru.examples import sentences
from spacy.lang.ru import Russian

Для проведения лемматизации создадим переменную, которая содержит языковую модель (отнеситесь к ней как к ещё одному типу данных) и загрузим данные для работы с русскоязычными текстами load("ru_core_news_sm").

nlp = Russian()
load_model = load("ru_core_news_sm")

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

lemma = []

for doc in load_model.pipe(author_data["text_clean"].values):
    lemma.append([n.lemma_ for n in doc])

author_data['text_clean_lemma'] = lemma
author_data[['text_clean','text_clean_lemma']].head()

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

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

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

Установить nltk и другие пакеты можно так же как и любые другие пакеты.

!pip install nltk

Вам необходимо использовать метод nltk.download('stopwords'), чтобы загрузить список стоп-слов для русского языка. Это действие нужно выполнить один раз: в дальнейшем списки будут лежать локально, на вашем компьютере, в папке nltk.

import nltk
nltk.download('stopwords')

Из всего корпуса стоп-слов нужно указать необходимый язык для анализа. Нам требуется русскоязычный корпус частотных слов, поэтому загружаем нужный нам набор с помощью метода stopwords.words("russian") в переменную stopwords_ru. Это переменная – список, поэтому просто выведем первые десять его значений.

from nltk.corpus import stopwords
stopwords_ru = stopwords.words("russian")
stopwords_ru[0:11]
['и', 'в', 'во', 'не', 'что', 'он', 'на', 'я', 'с', 'со']

Лемматизируем текст и объединим отдельных слова в строку.

author_data['text_clean_lemma'] = author_data['text_clean_lemma'].apply(lambda x: [item for item in x if item not in stopwords_ru])
author_data['text_clean_lemma_as_str'] = [' '.join(map(str, l)) for l in author_data['text_clean_lemma']]
author_data['text_clean_lemma_as_str']
0                                  эх господь мочь жить
1                                       пустой говорить
2     поспешность надутый собою человек кольнула сте...
3     самое лицо дом весёлый стекло окно блестели яс...
4              говорить дядечка лишний любовь андреевна
Name: text_clean_lemma_as_str, dtype: object

Мы видим, что исчезли местоимения и некоторый другие частотные слова.

Токенизация

Токенизация реализована в пакете nltk. Отвлечемся от нашего датасета и посмотрим на два простых примера: предложение и абзац.

from nltk.tokenize import word_tokenize
text = 'Надеюсь, дождь не помешает веселью'
tokenize = word_tokenize(text)
print(tokenize)
['Надеюсь', ',', 'дождь', 'не', 'помешает', 'веселью']

word_tokenize() — функция, которая делит текст на токены. На выходе мы получаем список, состоящий из отдельных слов и знаков препинания, которые тоже оказываются токенами. Для преобразования токенов в униграммы или биграммы следует удалить знаки препинания, стоп-слова и лишние знаки препинания.

Для проведения токенизации по предложениям вам необходимо подгрузить функцию sent_tokenize. Перед токенизацией текста по предложениям нельзя удалять знаки препинания. Без них нельзя будет корректно разделить текст.

Сохраняем в переменную text первый абзац из произведения «Шинель» Николая Гоголя и поделим по предложениям.

from nltk.tokenize import sent_tokenize
text = 'В департаменте… но лучше не называть, в каком департаменте. Ничего нет сердитее всякого рода департаментов, полков, канцелярий и, словом, всякого рода должностных сословий. Теперь уже всякий частный человек считает в лице своем оскорбленным все общество.'
tokenize = sent_tokenize(text)
print(tokenize)
['В департаменте… но лучше не называть, в каком департаменте.',
'Ничего нет сердитее всякого рода департаментов, полков, канцелярий и, словом, всякого рода должностных сословий.',
'Теперь уже всякий частный человек считает в лице своем оскорбленным все общество.']

Для разделения текста на биграммы или триграммы необходимо из пакета nltk загрузить ngrams. Мы просто комбинируем результат split() с методом ngrams(), в котором укаываем число элементов в каждой n-грамме.

from nltk import ngrams
text = 'В департаменте… но лучше не называть, в каком департаменте.'
grams = ngrams(text.split(), 2)

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

for gram in grams:
    print(gram)
('В', 'департаменте…')
('департаменте…', 'но')
('но', 'лучше')
('лучше', 'не')
('не', 'называть,')
('называть,', 'в')
('в', 'каком')
('каком', 'департаменте.')

Мы видим, что в биграммах каждое слово, кроме первого и последнего, фигурирует дважды: сначала как второе слово в паре, потом как первое.

Извлечение именованных сущностей

Для извлечения именованных сущностей мы будем использовать пакет SpaCy. Для начала работы необходимо подгрузить SpaCy для русскоязычных текстов (для загрузки русскоязычной модели используйте python -m spacy download ru_core_news_sm). Для работы с русскоязычными текстами загружаем соответствующую модель load("ru_core_news_sm").

import spacy as spacy
from spacy import load
from spacy.lang.ru.examples import sentences
from spacy.lang.ru import Russian
load_model = spacy.load('ru_core_news_sm')

Поиск именованных сущностей проводится с помощью метода text(), а присвоение именованной сущности тега проводится с помощью label_.

С помощью spaCy для русскоязычного текста извлекаются имена или фамилии личностей (под тегом PER), определение местоположения или локации(под тегом LOC) и названий организаций (под тегом ORG).

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

Мы ищем в каждой строке переменной именованную сущность и ее тип (тег) и добавляем найденные значения в созданные списки.

post = []
texts = []
for doc in load_model.pipe(author_data['text'].values):
    for entity in doc.ents:
        texts.append(entity.text)
        post.append(entity.label_)

Полученные списки можно объединить в набор данных.

ents_data = pd.DataFrame({'post':post, 'text':texts})
print(ents_data.head())
  post                 text
0  PER  Степана Трофимовича
1  PER           Кармазинов
2  PER     Любовь Андреевна
3  PER             Бехтерев
4  LOC              Нижнего

Первые четыре найденные именованные сущности относятся к типу PER (имена или фамилии личностей). Можно выделить определения локаций и названия городов, которые были определены правильно.

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

Анализ тональности для русскоязычного текста мы будем проводить с помощью библиотеки для анализа настроений Dostoevsky. Для использования библиотеки нужно подгрузить модель FastTextSocialNetworkModel и загрузить RegexTokenizer.

from dostoevsky.tokenization import RegexTokenizer
from dostoevsky.models import FastTextSocialNetworkModel

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

tokenizer = RegexTokenizer()
model = FastTextSocialNetworkModel(tokenizer=tokenizer)
data1 = model.predict(author_data['text'], k=5)

Через model.predict() мы предсказываем оценку вероятности для каждой строки по пяти категориям. Первый аргумент – это данные по которым предсказывается тональность, а второй — количество категорий тональности.

В Dostoevsky выделяются пять категорий тональности:

  • neutral – нейтральная тональность;
  • negative – негативная тональность;
  • positive – позитивная тональность;
  • speech – речь, в которой отсутствуют эмоционально окрашенные слова (приветствия, поздравления, прощания);
  • skip – отсутствие четко-выраженной тональности.

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

[{'positive': 0.26285186409950256,
'neutral': 0.20182321965694427,
'skip': 0.16027602553367615,
'negative': 0.14034625887870789,
'speech': 0.003085370408371091}]

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

neutral_list = []
negative_list = []
positive_list = []

for sentiment in data1:
        neutral_list.append(sentiment.get('neutral'))
        negative_list.append(sentiment.get('negative'))
        positive_list.append(sentiment.get('positive'))

Полученные списки мы прикрепляем к первоначальному корпусу классиков русских писателей.

author_data['neutral'] = neutral_list
author_data['negative'] = negative_list
author_data['positive'] = positive_list

Оценка тональности показывает вероятность негативной, нейтральной и позитивной тональности. Где единица означает наибольшую вероятность полученной тональности, а 0 наименьшую. Например, во фразе Эх, господа, как вы можете тут жить! определяется в качестве нейтральной на 20%, негативной на 14%, а позитивной на 26%.

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

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

Отмечайте параграфы как прочитанные чтобы видеть свой прогресс обучения

Вступайте в сообщество хендбука

Здесь можно найти единомышленников, экспертов и просто интересных собеседников. А ещё — получить помощь или поделиться знаниями.
Вступить
Сообщить об ошибке
Предыдущий параграф12.2. Методы извлечения данных из текстов
Следующий параграф13.1. Об основных источниках данных для исследований