Продолжаем осваивать NLP — направление в программировании по работе с естественными языками. Проще говоря, учим компьютер разбирать текст на слова и работать с ними так, как нам нужно. В первой части мы сделали много интересного:
- загрузили все тома «Войны и мира» в Python;
- очистили текст от запятых, предлогов и переносов строк;
- установили и настроили библиотеку NLTK для работы с текстом;
- убрали из текста русские и французские стоп-слова (например, артикли и предлоги);
- нашли самые частые слова в каждом томе;
- нарисовали облако слов по популярности — чем чаще встречается слово, тем крупнее оно написано.
В чём проблема такого подхода
Во время обработки текста мы считали все слова по их количеству — сколько раз слово встречается в тексте. С этой точки зрения слова «который», «которая» и «которые» — это разные слова, хотя это одно слово, просто в разных лицах и числах.
С точки зрения частоты употребления такой подход не годится — он распыляет своё внимание на одинаковых по смыслу, но разных по написанию словах, поэтому облако слов получается неточным и разношёрстным.
Что мы сделаем: короткая версия
Чтобы каждое облако тегов максимально точно отражало суть происходящего в каждой книге, мы сделаем так:
- приведём каждое слово в тексте к нормальной форме;
- построим новое облако слов для каждого тома;
- найдём общие самые частые слова, которые есть в каждом томе;
- уберём эти слова, если они есть в каждом облаке, чтобы они не мешали найти суть происходящего в книгах;
- нарисуем новые облака слов, которые будут отражать уникальный сюжет каждой книги.
Зачем это всё?
То, что мы сделаем, — типичная задача аналитика, которому нужно убрать шелуху и оставить только самое важное. Если вы хотели попробовать себя в роли аналитика, но не знали, с чего начать, — попробуйте с этого.
Приводим слова в нормальную форму
Все изменения будем делать в уже готовом скрипте, который мы собрали в первой части:
# открываем текстовый файл
f = open('tom1.txt', "r", encoding="utf-8")
# закидываем его содержимое в переменную
text = f.read()
# выводим начало, чтобы убедиться, что всё считалось правильно
print(text[:300])
# переводим символы в нижний регистр, чтобы всё было одинаково
text = text.lower()
# подключаем встроенный модуль работы со строками
import string
# добавляем к стандартным знакам пунктуации кавычки и многоточие
spec_chars = string.punctuation + '«»\t—…’'
# очищаем текст от знаков препинания
text = "".join([ch for ch in text if ch not in spec_chars])
# подключаем регулярные выражения
import re
# меняем переносы строк на пробелы
text = re.sub('\n', ' ', text)
# убираем из текста цифры
text = "".join([ch for ch in text if ch not in string.digits])
# смотрим на результат
print(text[:300])
# из библиотеки обработки текста подключаем модуль для токенизации слов
from nltk import word_tokenize
# токенизируем текст
text_tokens = word_tokenize(text)
# подключаем библиотеку для работы с текстом
import nltk
# переводим токены в текстовый формат
# text = nltk.Text(text_tokens)
text = nltk.Text(text_tokens)
# подключаем статистику
from nltk.probability import FreqDist
# и считаем слова в тексте по популярности
fdist = FreqDist(text)
# выводим первые 5 популярных слов
print(fdist.most_common(5))
# подключаем модуль со стоп-словами
from nltk.corpus import stopwords
# добавляем русские и французские стоп-слова
russian_stopwords = stopwords.words("russian")
russian_stopwords += stopwords.words("french")
# перестраиваем токены, не учитывая стоп-слова
text_tokens = [token.strip() for token in text_tokens if token not in russian_stopwords]
# снова приводим токены к текстовому виду
text = nltk.Text(text_tokens)
# считаем заново частоту слов
fdist_sw = FreqDist(text)
# показываем самые популярные
print(fdist_sw.most_common(10))
# добавляем свои слова в этот список
russian_stopwords.extend(['это', 'что','всё','сказал', 'сказала','говорил','говорила'])
# перестраиваем токены, не учитывая стоп-слова
text_tokens = [token.strip() for token in text_tokens if token not in russian_stopwords]
# снова приводим токены к текстовому виду
text = nltk.Text(text_tokens)
# считаем заново частоту слов
fdist_sw = FreqDist(text)
# показываем самые популярные
# подключаем библиотеку для создания облака слов
from wordcloud import WordCloud
# и графический модуль, с помощью которого нарисуем это облако
import matplotlib.pyplot as plt
import matplotlib.pyplot as plt1
# переводим всё в текстовый формат
text_raw = " ".join(text)
# готовим размер картинки
wordcloud = WordCloud(width=1600, height=800).generate(text_raw)
plt.figure( figsize=(20,10), facecolor='k')
# добавляем туда облако слов
plt.imshow(wordcloud)
# выключаем оси и подписи
plt.axis("off")
# убираем рамку вокруг
plt.tight_layout(pad=0)
# выводим картинку на экран
plt.show()
Нормальная форма слова — это то, как оно записано в словаре:
- для глаголов это будет неопределённая форма;
- для существительных — единственное число, именительный падеж;
- для прилагательных — единственное число, именительный падеж, мужской род.
После такой обработки слова «сказал», «сказали» и «скажут» превратятся в «сказать» — и у нас появится три одинаковых слова, вместо трёх разных. Для этого установим библиотеку с поддержкой русского языка pymorphy2:
pip install pymorphy2
Перед тем как продолжить, найдём в коде такую команду, чтобы добавить код после неё:
# токенизируем текст
text_tokens = word_tokenize(text)
Теперь подключаем библиотеку для нормализации и переводим все слова в нормальную форму:
# подключаем библиотеку для нормализации слов
import pymorphy2
# добавляем анализатор слов
morph = pymorphy2.MorphAnalyzer()
# тут будут те же самые слова, что и в исходном тексте, но в нормальной форме
filtered_tokens = []
# перебираем все слова в исходном тексте
for token in text_tokens:
# получаем нормальную форму текущего слова
p = morph.parse(str(token))[0]
# добавляем его в новый массив
filtered_tokens.append(p.normal_form)
Теперь нам нужно перевести в текстовый формат уже новые слова, поэтому меняем переменную в команде ниже:
# переводим токены в текстовый формат
text = nltk.Text(filtered_tokens)
И последнее, где нам нужно учесть работу с новой переменной, — это в перестройке токенов:
# перестраиваем токены, не учитывая стоп-слова
text_tokens = [token.strip() for token in filtered_tokens if token not in russian_stopwords]
Вот картинка для понимания, где именно нужно заменить в коде название переменной:
Сравниваем слова из четырёх томов
После запуска скрипта видно, что результат сильно отличается от того, что мы получили в первый раз: нормализация расставила всё по местам и показала настоящую частоту слов.
Прогоним через скрипт все тома и посмотрим, есть ли в облаках одинаковые слова, которые забивают собой все остальные:
Видно, что «который», «свой», «сказать» и «говорить» — самые частые слова в каждом томе. Это понятно: для Толстого это, по сути, служебные слова. Но если они есть везде, то, избавившись от них, мы получим более точное отражение того, что происходит в книгах.
У нас есть команда в скрипте, которая убирает ненужные слова из текста:
russian_stopwords.extend(['это', 'что','всё','сказал', 'сказала','говорил','говорила'])
Поменяем этот список и добавим в него такие слова:
- который,
- свой,
- говорить,
- сказать,
- думать,
- человек,
- ещё,
- весь.
У нас проявились ещё несколько кандидатов для добавления в фильтр, которые перетягивают внимание на себя:
- лицо,
- время,
- мочь,
- знать,
- видеть.
Добавим их в фильтр и запустим всё заново:
Видно, что в первом томе мы знакомимся со всеми героями понемногу, потом фокусируемся вокруг злоключений Пьера и Наташи, далее в третьем томе наконец-то им составит компанию Наполеон. А в четвёртом томе главными героями снова станут Пьер и Наташа. Нет сомнений, что «Война и мир» гораздо больше про мир, чем про войну.
Было → стало
Сравните сами две картинки: это слова из первого том без обработки и после анализа и добавления фильтров. В первой картинке всё внимание съели основные глаголы действия и «свой», а на второй видна суть книги.
Что дальше
То, что мы отобрали слова вручную, — иногда не самое лучшее решение. Гораздо круче было бы автоматически отфильтровать тексты так, чтобы в первой десятке популярных слов из каждого тома не было ни одного одинакового слова. Этим займёмся в следующий раз — нужно будет сильно кромсать и переделывать скрипт.