{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Основы программирования в Python\n", "\n", "*Алла Тамбовцева*" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Работа с текстом. Продолжение. Библиотека `pymorphy2`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Для работы импортируем библиотеку `pandas` и загрузим несколько отрывков лекций, взятых с [ПостНауки](https://postnauka.ru/), из файла *csv*:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import pandas as pd" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
text
0Что такое сверхтекучесть? Нам известно одно ее...
1Тут возникают новые вопросы о том, как вообще ...
2Еще одно интересное употребление редупликации ...
3Как устроены эти редупликации типа «маслице-фи...
4Курс «Расстройства личности: от паранойи и ист...
5Сопротивление соблазну. В экономических термин...
6В этом смысле такое картирование может быть ос...
7Существует широко распространенное убеждение, ...
\n", "
" ], "text/plain": [ " text\n", "0 Что такое сверхтекучесть? Нам известно одно ее...\n", "1 Тут возникают новые вопросы о том, как вообще ...\n", "2 Еще одно интересное употребление редупликации ...\n", "3 Как устроены эти редупликации типа «маслице-фи...\n", "4 Курс «Расстройства личности: от паранойи и ист...\n", "5 Сопротивление соблазну. В экономических термин...\n", "6 В этом смысле такое картирование может быть ос...\n", "7 Существует широко распространенное убеждение, ..." ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df = pd.read_csv('articles.csv', encoding= 'UTF-8')\n", "df" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'Как устроены эти редупликации типа «маслице-фигаслице»? Они устроены очень интересно с фонетической точки зрения. Мы берем слово и дальше, чтобы его повторить, берем эту бранную часть, скажем «фиг», а потом добавляем кусок, начиная от ударного гласного и далее ― «маслице-фигаслице». Вот несколько вполне реальных примеров из текстов в интернете: «комиссии-фигиссии», «анализы-фигализы», «тренды-фигенды», «бусики-фигусики». Правило строго соблюдается, это видно. В «комиссии» ударный гласный «и», соответственно, «комиссии-фигиссии». Это, конечно, не вполне удобно в ситуации, когда у нас ударный гласный находится, например, в конце слова. Какие-нибудь «дураки» по этому правилу ― должно было бы получиться «дураки-фиги». Так обычно не бывает, тогда добавляется еще слог перед этим, например «дураки-фигаки».'" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df.text[3] # отрывок из лекции А.Пиперски (https://postnauka.ru/video/77566)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Задание.** Взять функцию `normalize()` из прошлой части и создать новый столбец `text_norm` с нормализованным текстом с помощью метода `.apply()`. \n", "\n", "*Решение:*" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "import string\n", "def normalize(x):\n", " to_remove = string.punctuation + '«»—'\n", " translator = str.maketrans('', '', to_remove)\n", " res = x.translate(translator)\n", " res = res.lower()\n", " return res\n", "\n", "df['text_norm'] = df.text.apply(normalize)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Задание**. Написать функцию `split_text()` для разбиения текста (строки) по пробелу. Пусть функция разбивает строку по пробелу, удаляет лишние пробелы в получившихся словах и возвращает список слов.\n", "\n", "С помощью `.apply()` применить `split_text()` к столбцу `text_norm` ‒ создать новый столбец `words`.\n", "\n", "*Решение:*" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "def split_text(text):\n", " res = text.split(\" \")\n", " final = [r.strip() for r in res]\n", " return final\n", " \n", "df['words'] = df.text_norm.apply(split_text)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Задание.** Написать функцию `filter_words()` для разбиения текста на слова с помощью `tokenize` из `nltk` и `stopwords` для фильтрации стоп-стоп. Функция должна возвращать список слов с исключенными стоп-словами.\n", "\n", "*Решение:*" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "from nltk.tokenize import sent_tokenize, word_tokenize\n", "from nltk.corpus import stopwords\n", "\n", "def filter_words(text, lang = 'russian'):\n", " \n", " wordsFiltered = []\n", " stopWords = set(stopwords.words(lang))\n", " words = word_tokenize(text)\n", "\n", " for w in words:\n", " if w not in stopWords:\n", " wordsFiltered.append(w)\n", " return wordsFiltered" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Все эти функции нам понадобятся далее, а пока остановимся и познакомимся с [библиотекой](https://pymorphy2.readthedocs.io/en/latest/) `pymorphy2`. Библиотека `pymorphy2` разработана М.Коробовым специально для русского и украинского языков. Импортируем библиотеку и рассмотрим несколько несложных примеров (пока не связанных с нашими текстами)." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "import pymorphy2\n", "morph = pymorphy2.MorphAnalyzer() # морфологический анализатор - класс MorphAnalyzer()" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Parse(word='стекла', tag=OpencorporaTag('NOUN,inan,neut sing,gent'), normal_form='стекло', score=0.964285, methods_stack=((, 'стекла', 545, 1),))" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "p = morph.parse('стекла')[0] # индекс 0 добавлен для извлечения самого слова\n", "p" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Морфологический анализатор ‒ набор средств для морфологического разбора слова. Класс, указанный выше, позволит не только извлечь необходимую грамматическую информацию, но и вернуть нормальную (начальную) форму слова, поставить его в нужный падеж, согласовать с числительным и прочее. Проверим." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[Parse(word='стекло', tag=OpencorporaTag('NOUN,inan,neut sing,nomn'), normal_form='стекло', score=1.0, methods_stack=((, 'стекло', 545, 0),)),\n", " Parse(word='стекла', tag=OpencorporaTag('NOUN,inan,neut sing,gent'), normal_form='стекло', score=1.0, methods_stack=((, 'стекла', 545, 1),)),\n", " Parse(word='стеклу', tag=OpencorporaTag('NOUN,inan,neut sing,datv'), normal_form='стекло', score=1.0, methods_stack=((, 'стеклу', 545, 2),)),\n", " Parse(word='стекло', tag=OpencorporaTag('NOUN,inan,neut sing,accs'), normal_form='стекло', score=1.0, methods_stack=((, 'стекло', 545, 3),)),\n", " Parse(word='стеклом', tag=OpencorporaTag('NOUN,inan,neut sing,ablt'), normal_form='стекло', score=1.0, methods_stack=((, 'стеклом', 545, 4),)),\n", " Parse(word='стекле', tag=OpencorporaTag('NOUN,inan,neut sing,loct'), normal_form='стекло', score=1.0, methods_stack=((, 'стекле', 545, 5),)),\n", " Parse(word='стёкла', tag=OpencorporaTag('NOUN,inan,neut plur,nomn'), normal_form='стекло', score=1.0, methods_stack=((, 'стёкла', 545, 6),)),\n", " Parse(word='стёкол', tag=OpencorporaTag('NOUN,inan,neut plur,gent'), normal_form='стекло', score=1.0, methods_stack=((, 'стёкол', 545, 7),)),\n", " Parse(word='стёклам', tag=OpencorporaTag('NOUN,inan,neut plur,datv'), normal_form='стекло', score=1.0, methods_stack=((, 'стёклам', 545, 8),)),\n", " Parse(word='стёкла', tag=OpencorporaTag('NOUN,inan,neut plur,accs'), normal_form='стекло', score=1.0, methods_stack=((, 'стёкла', 545, 9),)),\n", " Parse(word='стёклами', tag=OpencorporaTag('NOUN,inan,neut plur,ablt'), normal_form='стекло', score=1.0, methods_stack=((, 'стёклами', 545, 10),)),\n", " Parse(word='стёклах', tag=OpencorporaTag('NOUN,inan,neut plur,loct'), normal_form='стекло', score=1.0, methods_stack=((, 'стёклах', 545, 11),))]" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "p.lexeme" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Лексема ‒ слово как абстрактная единица. Проще говоря, это начальная форма слова и все его возможные грамматические формы (лингвисты называют такой набор форм парадигмой). В случае существительных в парадигму входят все падежные формы слова в единственном и множественном числе, что мы здесь и видим. На последнем месте в `tag` указан падеж: \n", "* именительный (*nominative*)\n", "* родительный (*genitive*)\n", "* дательный (*dative*)\n", "* винительный (*accusative*)\n", "* творительный (*ablative*)\n", "* предложеный (*locative*, то есть местный)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Можно запросить нормальную (начальную) форму слова, то есть привести слово к тому виду, с каким мы уже сталкивались после лемматизации." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'стекло'" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "p.normal_form" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Можно вывести все грамматические тэги, то есть все грамматические характеристики слова:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "OpencorporaTag('NOUN,inan,neut sing,gent')" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "p.tag" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Или не все, а конкретные (`POS` ‒ *Part of Speech*, часть речи):" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'NOUN'" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "p.tag.POS" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Можно поставить слово в форму конкретного падежа:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Parse(word='стеклом', tag=OpencorporaTag('NOUN,inan,neut sing,ablt'), normal_form='стекло', score=1.0, methods_stack=((, 'стеклом', 545, 4),))" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "p.inflect({'ablt'}) # творительный" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'стеклом'" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "p.inflect({'ablt'}).word # само слово" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Приятно то, что в анализатор `pymorphy2` встроен алгоритм, который позволяет предсказывать грамматические характеристики неизвестного, несловарного слова. В официальной документации приведено очаровательное слово \"бутявка\" (кто не в курсе, откуда она взялась, посмотрите лингвистические [сказки](http://lib.ru/PROZA/PETRUSHEWSKAYA/butyawka.txt) Людмилы Петрушевской)." ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Parse(word='бутявка', tag=OpencorporaTag('NOUN,inan,femn sing,nomn'), normal_form='бутявка', score=1.0, methods_stack=((, 'явка', 8, 0), (, 'бут')))" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "butyavka = morph.parse('бутявка')[0]\n", "butyavka" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Анализатор сообщит, что с наибольшей вероятностью это слово является существительным женского рода, стоящее в именительном падеже. Более того, он даже сможет согласовывать это слово с числительными. Например, с числом *3*:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'бутявки'" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "butyavka.make_agree_with_number(3).word # три бутявки" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Кстати, о вероятностях. Анализатор построен на механизмах машинного обучения и статистических моделях. На основе большого массива размеченных текстов (см. подробнее в документации) построена модель, которая позволяет оценивать вероятность, с какой слово можно отнести к определенной части речи с некоторыми грамматическими характеристиками.\n", "Рассмотрим слово *стекло*:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[Parse(word='стекло', tag=OpencorporaTag('NOUN,inan,neut sing,nomn'), normal_form='стекло', score=0.75, methods_stack=((, 'стекло', 545, 0),)),\n", " Parse(word='стекло', tag=OpencorporaTag('NOUN,inan,neut sing,accs'), normal_form='стекло', score=0.1875, methods_stack=((, 'стекло', 545, 3),)),\n", " Parse(word='стекло', tag=OpencorporaTag('VERB,perf,intr neut,sing,past,indc'), normal_form='стечь', score=0.0625, methods_stack=((, 'стекло', 968, 3),))]" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "morph.parse('стекло')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "На первом месте стоит вариант, *score* (вероятность варианта) которого наибольший. В нашем случае слово *стекло* c 75% уверенностью можно считать существительным в единственном числе и в именительном падеже, с уверенностью 18.75% ‒ существительным в единственном числе и в винительном падеже, с уверенностью 6.25% ‒ глаголом (форма *стечь*)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Вернемся к нашим текстам. Давайте попробуем составить небольшой список ключевых слов и выбирать среди текстов те, в которых встречаются эти слова. Тут возможно два пути: привести все слова к нормальнойй форме (лемматизация) и искать среди них или составить список, включающий все формы ключевых слов. Мы пойдем по второму пути. Он не всегда рациональный (особенно, если ключевых слов много), но тоже по-своему может быть полезен.\n", "\n", "Для простоты возьмем одно слово, слово *сопротивление* и сгенерируем все его формы." ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Parse(word='сопротивление', tag=OpencorporaTag('NOUN,inan,neut sing,nomn'), normal_form='сопротивление', score=0.666666, methods_stack=((, 'сопротивление', 76, 0),))" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "s = morph.parse('сопротивление')[0]\n", "s" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['сопротивление',\n", " 'сопротивленье',\n", " 'сопротивления',\n", " 'сопротивленья',\n", " 'сопротивлению',\n", " 'сопротивленью',\n", " 'сопротивление',\n", " 'сопротивленье',\n", " 'сопротивлением',\n", " 'сопротивленьем',\n", " 'сопротивлении',\n", " 'сопротивленье',\n", " 'сопротивленьи',\n", " 'сопротивления',\n", " 'сопротивленья',\n", " 'сопротивлений',\n", " 'сопротивлениям',\n", " 'сопротивленьям',\n", " 'сопротивления',\n", " 'сопротивленья',\n", " 'сопротивлениями',\n", " 'сопротивленьями',\n", " 'сопротивлениях',\n", " 'сопротивленьях']" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "lexemes = [s.lexeme[i].word for i in range(len(s.lexeme))]\n", "lexemes # и даже с разным написанием" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Задание.** Напишите код, который выбирает из `df` только те строки, где в `words` встречаются слова из списка `lexemes`.\n", "\n", "*Решение:*" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [], "source": [ "with_keys = []\n", "\n", "for i in range(0, len(df)):\n", " for w in df.iloc[i].words:\n", " if w in lexemes:\n", " with_keys.append(df.iloc[i].text)" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['Что такое сверхтекучесть? Нам известно одно ее проявление — способность вещества течь без трения. Если вы возьмете ведро гелия и поместите его в любую емкость, вы можете двигать его вверх и вниз, а между стенками емкости не возникнет никакого трения. Сверхтекучесть была открыта в гелии-4 и объяснена при помощи конденсата Бозе — Эйнштейна, потому что атомы гелия-4 существуют в форме бозонов и могут формировать микроскопическое квантовое состояние — конденсат Бозе — Эйнштейна. Обычно конденсаты Бозе — Эйнштейна — это и есть сверхтекучие материалы. Тем не менее их не следует сводить друг к другу. Например, в сверхтекучем гелии-4 сила взаимодействия намного больше, чем в других сверхтекучих материалах. Поэтому общая картина немного более сложная.В целом в состоянии сверхтекучести частицы складываются в микроскопическое квантовое состояние и действуют коллективно. Если они наталкиваются на препятствие, они не рассеиваются. Если смотреть на это с точки зрения энергии, то можно понять, что любые возбуждения вне этих микроскопических объектов энергетически невозможны, потому что соотношение между энергией и импульсом изменяется таким образом, что возбуждения не возникает. Этим объясняется тот факт, что сверхтекучая жидкость течет без сопротивления.',\n", " 'Сопротивление соблазну. В экономических терминах поведение группы характеризовалось динамической несогласованностью. Сначала А было предпочтительным относительно Б, а затем приоритет перешел к Б. Динамическая несогласованность — нередкое явление. Субботним утром люди заявляют, что лучше займутся физкультурой, чем будут сидеть перед телевизором. К полудню они все еще дома на диване, смотрят футбол. Чем объяснить такое поведение?\\nВ случае с кешью действуют два фактора: искушение и бездумность. Понятие искушения занимает человеческие умы по меньшей мере со времен Адама и Евы. Для теории подталкивания оно также важно. Что мы подразумеваем, когда говорим о соблазнительном, искушающем?',\n", " 'В этом смысле такое картирование может быть осуществлено любым механизмом, например биологической нейронной сетью, аналоговой или цифровой сетью или даже физическим контуром. Такими физическими контурами могут быть и опорно-двигательный аппарат живых существ, и различные механические структуры. Например, в экспериментах в моей лаборатории продемонстрировано, что роботизированный аналог копыта горного козла может иметь во много раз более высокое сопротивление скольжению за счет правильной мягкости основных связок по сравнению с таким же копытом, в котором суставы менее подвижны. Мы также заметили, что этот эффект меняется в зависимости от разных условий местности. Это пример того, как механический контур тела становится важным физическим свойством, в зависимости от условий среды определяющим состояния от скольжения (количественные характеристики) до сопротивления скольжению (количественные характеристики в другом пространстве).',\n", " 'В этом смысле такое картирование может быть осуществлено любым механизмом, например биологической нейронной сетью, аналоговой или цифровой сетью или даже физическим контуром. Такими физическими контурами могут быть и опорно-двигательный аппарат живых существ, и различные механические структуры. Например, в экспериментах в моей лаборатории продемонстрировано, что роботизированный аналог копыта горного козла может иметь во много раз более высокое сопротивление скольжению за счет правильной мягкости основных связок по сравнению с таким же копытом, в котором суставы менее подвижны. Мы также заметили, что этот эффект меняется в зависимости от разных условий местности. Это пример того, как механический контур тела становится важным физическим свойством, в зависимости от условий среды определяющим состояния от скольжения (количественные характеристики) до сопротивления скольжению (количественные характеристики в другом пространстве).']" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "with_keys" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.5.2" } }, "nbformat": 4, "nbformat_minor": 2 }