Pull to refresh

Создание частотного словаря на основе анализа библиотеки художественной литературы

Semantics
Sandbox
Общий привет.

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

Задачка, казалось бы весьма простая и наверняка решается при изучении программирования в первых рядах. Но для анализа такой громоздкой библиотеки, а используемая мной библиотека простирается на 157 гектаров, средств среднего домашнего компьютера хватает с натягом. Если сказать точнее, то библиотека хранится в пятидесяти zip архивах по 0.5 — 2 Гб, в каждом из которых 20-30 тыс. произведений в формате fb2. В сжатом виде весит она 60 Гб.

Решалась задачка на языке c#. Результат нужно получить за адекватное время, в качестве которого я выбрал не более 8 часов, чтобы можно было запустить исполнение вечером и получить результат утром.

Поиск решения

Массив

Самый очевидный метод решения, что называется «в лоб» — два массива, в первом — слова, во втором — число. Встретив новое слово, добавляем его в первый массив, если оно там отсутствует или прибавляем единичку к индексу во втором массиве, если нашли слово в первом. Опробовав данный вариант я незамедлительно в нем разочаровался, по прошествии нескольких часов программа скрипя ползла по первой половине первого — же архива. Любой профессиональный программист, наверняка уже смеется над таким подходом. Однако, признаюсь — я даже не предполагал что меня ждёт подвох.

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

Упорядоченный список

Пришлось искать такую структуру данных, которая позволит добавить каждый элемент просто в конец оной структуры, но позволит в то-же время их упорядочить. Тут-же я наткнулся на упорядоченные списки. В ячейке упорядоченного списка хранится сам кусочек данных и указатель на другую ячейку. Все превосходно, мы просто добавляем новое слово и меняем 2 индекса, индекс предыдущего слова, указывающий на данное и индекс данного слова, указывающие на следующее. Но, вот незадача, поиск в таком списке весьма вычислительно требователен. Если в упорядоченном массиве мы могли начинать поиск с середины и делить его пополам за одну итерацию, то в упорядоченном списке приходится карабкаться по указателям от одного к другому, как по ниточке, пока не найдем нужное место во всем клубке. Пробовать этот вариант я не стал, наученный предыдущим провалом я сразу пронюхал засаду.

Бинарное дерево поиска

Следующую структуру данных я нашел лишь через несколько часов. Мне попались бинарные деревья поиска. Немного почитав о различных вариантах я остановился на самобалансирующемся AVL дереве, созданном, кстати советскими учёными Адельсон-Вельским Георгием Максимовичем и Ландисом Евгением Михайловичем, и унаследовавшем от их фамилий своё название.

Структура бинарного дерева схожа с упорядоченным списком, но каждый элемент, кроме нескольких конечных (т.н. листьев) ссылается на два элемента. Корневой элемент — это тот, что находился бы в середине упорядоченного массива. Он ссылается на элемент меньший (левый) и больший (правый), кроме того — гарантированно, что все элементы левой ветви будут меньше данного, а все элементы правой ветви — больше. Рассмотрев левый или правый элемент, к которому мы пришли — мы увидим ту-же иерархию, он так-же ссылается на два элемента, левая ветвь так-же меньше, правая — больше. Чтобы понять способ балансировки такого дерева — лучше всего прочитать соответствующую статью, например тут: АВЛ-деревья. Важно заметить, что двоичное дерево поиска полностью удовлетворило моим требованиям. Быстрый поиск и быстрое добавление нового элемента.

Результат


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

Вот несколько строк по результатам работы программы, слева — слово, справа — частота. Это самые часто встречаемые слова различной длины:

  • я 34361402
  • а 36192092
  • с 52479822
  • в 126422246
  • и 158458227


  • по 23978868
  • он 32602346
  • то 42163693
  • на 83001625
  • не 97183097


  • все 19576264
  • это 21318968
  • его 27719894
  • как 30228770
  • что 63106338


  • даже 6789652
  • была 6832494
  • если 7750404
  • меня 12381969
  • было 15450767


  • может 5455561
  • очень 5477013
  • время 6317279
  • когда 9788599
  • чтобы 9987225


  • человеконенавистничества 296
  • высококвалифицированного 350
  • высокопревосходительству 384
  • высокопревосходительства 489
  • высокопревосходительство 3739


  • человеконенавистнического 46
  • человеконенавистничеством 52
  • частнопредпринимательской 60
  • высокопревосходительством 91
  • националсоциалистическата 96


В общем было получено 9.5 млн. слов, анализ длился 8482 сек., средняя скорость обработки распакованного текста — 18.57 мб/сек.

Теперь можно использовать полученные данные для доработки моего морфологического словарика, а заполучив словарик можно будет улучшить «частотный анализатор», т.к. появится возможность группировки однокоренных слов. Кроме того, доработки требует работа «частотного анализатора» с различными кодировками и т.п. Далее — синтаксический анализ предложения. В конечном итоге хочу заполучить сколь-нибудь адекватную семантическую сеть.

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

Всем спасибо.
Tags:компьютерная лингвистикаструктуры данныхоптимизация кода
Hubs: Semantics
Total votes 18: ↑15 and ↓3 +12
Views5.9K

Popular right now

SQL и получение данных
May 18, 202120,230 ₽Нетология
Аналитик данных
May 19, 202166,000 ₽Нетология
База данных Oracle 12c: Основы SQL
May 17, 202156,500 ₽Сетевая Академия ЛАНИТ
Курс "Анализ данных на Scala 4.0"
May 18, 202165,000 ₽New Professions Lab