В современном мире объем данных в интернете постоянно растет с огромной скоростью. Возникает логичный вопрос: как ориентироваться в этом информационном потоке?  Чтобы упростить себе задачу поиска и обобщения информации IT-энтузиасты применяют технологии генеративно обученных чат-ботов. Наиболее широкое распространение получил  ChatGPT. Яндекс, в свою очередь, добавил в браузер YandexGPT, который позволяет тезисно ознакомиться с содержанием страницы. Всё чаще вакансия Prompt-инженера начинает встречаться на hh и Хабр Карьере. Специалисты и чат-боты помогают конечному пользователю экономить время для поиска необходимой информации.  Но что делать, если возможности обратиться за помощью к подобным технологиям нет? Указанные выше языковые модели нельзя интегрировать в собственные проекты, сценариев их использования много, но они всё равно ограничены.  В статье мы расскажем, как (не без нейронных сетей) можно создать простой алгоритм на Python, который поможет извлекать ключевые слова из любого текста, тем самым избавляться от ненужной информации и автоматизировать процесс анализа материалов. Мы будем работать с русским текстом, а именно —  с новостными постами. Поэтому в частном случае используются пакеты для обработки, поддерживающие именно русский язык. В том числе используются модели, обученные на корпусах текстов с новостной семантикой.  Как понять, что именно необходимо “выкидывать” из оригинального текста? Стоит обратиться к одному из разделов языкознания — синтаксическому анализу. Если отвечать на вопрос, что именно в тексте передает основную информацию, то можно выделить: грамматическую основу предложения, подлежащее + сказуемое;    синтаксические единицы, словосочетания.    В первом случае подлежащим и сказуемым чаще всего выступают существительное и глагол (в некоторых частных случаях подлежащим также может выступать глагол в форме инфинитива, а сказуемым — прилагательное или существительное). Во втором же случае, помимо упомянутых выше частей речи, встречаются еще и причастия. Возникает вопрос: не напрасно ли обделены вниманием местоимения и наречия? Чтобы понять, почему мы не рассматриваем эти части речи, нужно еще раз оценить, как именно должен работать потенциальный алгоритм. В конечном итоге мы решаем задачу извлечения основной информации из отдельно взятого предложения. В таком случае в паре подлежащего и сказуемого “он сказал” местоимение не несет никакой смысловой нагрузки. Обособленно от всего текста невозможно понять, о ком или о чем идет речь. Если же рассматривать наречия, обозначающие признак действия, предмета или другого признака, то в контексте анализа новостных блоков нам неважно, как именно субъект выполнял те или иные действия.  Таким образом, наш алгоритм можно разбить на 2 этапа: Определение частей речи в тексте. Построение комбинаций слов в словосочетания.   В языкознании принято считать, что подлежащее и сказуемое не являются словосочетанием. Однако в данной статье мы будем трактовать их как словосочетание для упрощения восприятия. С этого момента будет меньше воды и (наконец-то!!!) больше описания технических аспектов реализации. Хранить информацию о входном тексте, результате его сегментации, выделенных ключевых словах и словосочетаниях мы будем в коллекции элементов doc.  Для определения частей речи в тексте необходимо сначала разбить текст на отдельные слова — токены. Чтобы не “изобретать велосипед”, мы воспользуемся функционалом библиотеки razdel, которая помогает сегментировать русский текст, а именно:  sentenize() для сегментации текста на предложения; tokenize() для токенизации слов внутри предложения.   В doc по ключу text будет храниться оригинальный текст, а в tokens — перечень  со списками всех слов.   Перед тем, как приступить к определению части речи каждого слова, необходимо добавить фильтрацию на стоп-слова (в русском языке это в основном союзы и предлоги). Такие слова встречаются почти в каждом предложении и не несут большой информативной нагрузки, не влияют на построение ключевых словосочетаний, но добавляют много шума. Составлять список таких слов вручную не придется — достаточно воспользоваться готовыми библиотеками. Му обратимся к NLTK, в которой имеется список стоп-слов для русского языка. Его можно скачать следующим образом: После этого приступаем  к созданию функции для морфологического анализа. Непосредственно определение части речи отдельного токена будет производиться с помощью MorphAnalyzer() библиотеки pymorphy2. Для того, чтобы фильтр по частям речи работал оптимальнее, перечень необходимых частей речи (Part Of Speech или POS) мы сохраним не в список, а во множество, так поиск по нему будет производиться быстрее. Учитываются следующие части речи: NOUN, существительное; ADJF и ADJS, полная и краткая форма прилагательного; VERB и INFN, глагол и инфинитив; PRTF и PRTS, полная и краткая форма причастия. В итоге по ключу candidates будет храниться коллекция токенов, которые прошли фильтрацию на часть речи. Для построения словосочетаний можно было бы просто пройтись скользящим окном по каждому предложению и объединить стоящие рядом слова. Однако в русском языке связанные слова могут находиться не рядом друг с другом. Чтобы автоматизировать процесс поиска связанных внутри предложения слов, мы решили провести синтаксический анализ всех предложений и определить семантические связи. Сначала  в директорию вашего проекта необходимо скачать модель navec_news_v1_1B_250K_300d_100q.tar, которая используется в библиотеке navec. С помощью неё мы сможем строить эмбеддинги русских слов. Конкретно эта модель обучена на 1 миллиарде слов, из которых был построен словарь в 250 тысяч уникальных слов. В конечном итоге каждое слово будет описываться вектором размерностью 100. Существует модель, которая обучена на двукратном объеме слов, однако для  нашей задачи достаточно и упрощенной  версии.  Синтаксический анализ будет проводиться за счет функционала пакета slovnet, а точнее модели slovnet_syntax_news_v1.tar, которую также необходимо предварительно скачать и поместить в директорию проекта. На примере одного предложения мы хотим продемонстрировать принципы работы синтаксической модели. В том числе, воспользуемся написанной ранее функцией segment_text(): Теперь в doc хранится словарь: Объявленная модель syntax должна принимать на вход токенизированное предложение, а не оригинальный текст (!!!), поэтому передадим в неё doc[“tokens”][0]: Получим: Помимо очевидного ключа text в syntax_markup.tokens хранится: id - индекс слова во входном предложении (начинается с 1); head_id - индекс родительского слова (в смысле семантической связи); rel - тип семантической связи. Можно увидеть, что у сказуемого “играла” нет родительского слова, так как оно является частью грамматической основы предложения. Соответственно, для построения словосочетаний на основе семантических связей достаточно обратиться к каждому элементу syntax_markup.tokens и определить родительское слово для текущего слова.  Для удобства поиска родительских слов создадим словарь вида {id_слова_в_предложении: само_слово}: Также добавим проверку, чтобы строить ключевые словосочетания только из ранее  отфильтрованных по частям речи слов. В итоге по ключу collocations функция будет сохранять все уникальные составленные словосочетания. Проверим, как работают наши функции: Получаем на выходе:   В итоге был получен относительно простой алгоритм, который способен анализировать текст на предмет построения ключевых словосочетаний. Мы хотим еще раз подчеркнуть два момента: Данный алгоритм не претендует на звание SOTA-решения. Это весьма тривиальный пайплайн, который позволяет построить словосочетания длинной в 2 слова. Для улучшения вы можете: а. добавить древовидную структуру построения словосочетаний, чтобы их длина увеличилась; b. добавить анализ типа семантических связей, чтобы учитывать правильный порядок слов в словосочетаниях. Несмотря на то, что использующиеся для построения эмбеддингов и синтаксического анализа модели способны работать с любым текстом, в первую очередь они ориентированы на новостные тексты. Если вы работает, к примеру, с художественной литературой, вам лучше скачать модель navec_hudlit_v1_12B_500K_300d_100q.tar.    Спасибо за внимание!    Разработчик ПО