Язык программирования Python

Предварительная обработка текстовых данных в Python для NLP.

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

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

Начнем с импорта.

import re, string, unicodedata import nltk import contractions import inflect from bs4 import BeautifulSoup from nltk import word_tokenize, sent_tokenize from nltk.corpus import stopwords from nltk.stem import LancasterStemmer, WordNetLemmatizer from num2words import num2words
Code language: JavaScript (javascript)

Помимо стандартных библиотек Python, мы также используем следующие библиотеки:

  • NLTK : Natural Language Toolkit – одна из самых популярных и широко используемых NLP библиотек в экосистеме Python, полезная для всего, начиная от токенизации, заканчивая стеммингом, тегированием части речи и т.д.
  • BeautifulSoup – BeautifulSoup – это полезная библиотека для извлечения данных из документов HTML и XML.
  • Inflect – Это простая библиотека для выполнения задач, связанных с естественным языком, по созданию множественного числа, единственного числа существительных и (самое главное) преобразованию чисел в слова.
  • Contractions – Еще одна простая библиотека, предназначенная только для расширения сокращений.
  • num2words – Служит для преобразования цифр в слова.

Если у вас не установлены библиотеки, вы можете сделать это командами:

pip install nltk pip install contractions pip install inflect pip install beautifulsoup pip install num2words

Нам нужны примеры текста. Мы начнем с чего-то очень маленького и искусственного, чтобы мы могли видеть результаты того, что мы делаем шаг за шагом.

sample = """<h1>Тут заголовок страницы</h1> <b>Жирный текст</b> <i>Текст курсивом</i> <img src="это все должно исчезнуть"/> <a href="это тоже исчезнет">Но это все еще будет здесь!</a> Я бегу. Он бежал. Она бежит. Перестанут ли они бегать? Я говорил. Она говорила. Они говорили с ними о беге. Кто побежал к говорящему бегуну? (Здесь находится текст, который мы не хотим сохранять) Иван, Сергей, Александр и Василий собираются в магазин завтра утром! Что-то... не так! с.,; этим :: предложением. Я больше не могу этого делать. Я не знал их. Почему ты не мог поужинать в ресторане? Мои любимые кинофраншизы, по порядку: Индиана Джонс; фильмы вселенной Марвел; Звездные войны; Назад в будущее; Гарри Поттер. Не делай этого.... Просто не делай. Билли! Я знаю, что ты делаешь. У тебя тут отличный домик. [Это какой-то другой ненужный текст] Джон: "Так, так, так". Джеймс: "Вот, вот. Там, там." &nbsp;&nbsp; Есть много причин не делать этого. Есть 101 причина не делать этого. 1000000 причин, на самом деле. Мне тоже нужно купить 2 пачки в двух разных магазинах. 22 45 1067 445 {{ Вот какие-то слова внутри двойных фигурных скобках}} {Здесь больше текста в одинарных фигурных скобках.} [DELETE] </body> </html>"""
Code language: HTML, XML (xml)

Конечно, это нелепый набор данных, но не стоит недооценивать: шаги, которые мы здесь предпринимаем для предварительной обработки этих данных, полностью переносимы.

Устранение шума

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

Типовые задачи по устранению шума могут включать:

  • удалить верхние и нижние регистры текстового файла
  • удалить разметку и метаданные HTML, XML и т.д.
  • извлечение важных данных из других форматов, таких как JSON

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

В нашем механизме предварительной обработки данных мы удалим HTML-разметку с помощью библиотеки BeautifulSoup и воспользуемся регулярными выражениями для удаления открытых и закрытых двойных скобок и любых промежуточных элементов (мы предполагаем, что это необходимо, основываясь на нашем примере)

def strip_html(text): soup = BeautifulSoup(text, "html.parser") return soup.get_text() def remove_between_square_brackets(text): return re.sub('\[[^]]*\]', '', text) def denoise_text(text): text = strip_html(text) text = remove_between_square_brackets(text) return text sample = denoise_text(sample) print(sample)
Code language: PHP (php)

Хотя это не обязательно делать на данном этапе перед токенизацией (вы увидите, что это утверждение является нормой для относительно гибкого упорядочивания задач предварительной обработки текстовых данных), замена сокращений на их расширения может быть полезной , поскольку наш токенизатор разделит слова типа “не мог” на “не” и “мог”. Не исключено, что эту токенизацию можно исправить на более позднем этапе, но сделать это раньше проще и понятнее.

def replace_contractions(text): """Replace contractions in string of text""" return contractions.fix(text) sample = replace_contractions(denoise_text(sample)) print(sample)
Code language: PHP (php)

А вот результат удаления шума в нашем примере текста.

Тут Жирныйный Текст Ноо! Я? Я? (Здесь, который Ивананн, Сергейгей, Александр! Что-тоо! с,; этимимм:: предложениемжениемием Я? Мои, поо: Индиана; фильмыьмы; Звездныедныеыее; Назад; Гарри Нее! Я, что Джононн: "Так, так, так". Джеймсймс: "Вот, вот. Там, там." Естьтьь101 причина1000000 причинчин, наа Мне2 пачки 22 45 1067 445 {{ Вот-тоо {Здесь
Code language: JavaScript (javascript)

Токенизация

Токенизация – это шаг, который разбивает длинные строки текста на более мелкие фрагменты или лексемы. Большие куски текста могут быть преобразованы в предложения, предложения – в слова и т.д. Дальнейшая обработка обычно выполняется после того, как кусок текста был правильно скомпонован. Токенизация также известна как сегментация текста или лексический анализ.

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

Для нашей задачи мы собираемся токенизировать наш образец текста в список слов. Для этого используется функция NTLK word_tokenize().

Примечание. Если у вас возникнет ошибка “Resource punkt not found” просто добавьте строку nltk.download(‘punkt’) в код и запустите. Система автоматически загрузит punkt. После чего эту строку можно удалить.

words = nltk.word_tokenize(sample) print(words)
Code language: PHP (php)

А вот наши токены для слов:

['Тут', 'заголовок', 'страницы', 'Жирный', 'текст', 'Текст', 'курсивом', 'Но', 'это', 'все', 'еще', 'будет', 'здесь', '!', 'Я', 'бегу', '.', 'Он', 'бежал', '.', 'Она', 'бежит', '.', 'Перестанут', 'ли', 'они', 'бегать', '?', 'Я', 'говорил', '.', 'Она', 'говорила', '.', 'Они', 'говорили', 'с', 'ними', 'о', 'беге', '.', 'Кто', 'побежал', 'к', 'говорящему', 'бегуну', '?', '(', 'Здесь', 'находится', 'текст', ',', 'который', 'мы', 'не', 'хотим', 'сохранять', ')', 'Иван', ',', 'Сергей', ',', 'Александр', 'и', 'Василий', 'собираются', 'в', 'магазин', 'завтра', 'утром', '!', 'Что-то', '...', 'не', 'так', '!', 'с.', ',', ';', 'этим', ':', ':', 'предложением', '.', 'Я', 'больше', 'не', 'могу', 'этого', 'делать', '.', 'Я', 'не', 'знал', 'их', '.', 'Почему', 'ты', 'не', 'мог', 'поужинать', 'в', 'ресторане', '?', 'Мои', 'любимые', 'кинофраншизы', ',', 'по', 'порядку', ':', 'Индиана', 'Джонс', ';', 'фильмы', 'вселенной', 'Марвел', ';', 'Звездные', 'войны', ';', 'Назад', 'в', 'будущее', ';', 'Гарри', 'Поттер', '.', 'Не', 'делай', 'этого', '....', 'Просто', 'не', 'делай', '.', 'Билли', '!', 'Я', 'знаю', ',', 'что', 'ты', 'делаешь', '.', 'У', 'тебя', 'тут', 'отличный', 'домик', '.', 'Джон', ':', '``', 'Так', ',', 'так', ',', 'так', "''", '.', 'Джеймс', ':', '``', 'Вот', ',', 'вот', '.', 'Там', ',', 'там', '.', "''", 'Есть', 'много', 'причин', 'не', 'делать', 'этого', '.', 'Есть', '101', 'причина', 'не', 'делать', 'этого', '.', '1000000', 'причин', ',', 'на', 'самом', 'деле', '.', 'Мне', 'тоже', 'нужно', 'купить', '2', 'пачки', 'в', 'двух', 'разных', 'магазинах', '.', '22', '45', '1067', '445', '{', '{', 'Вот', 'какие-то', 'слова', 'внутри', 'двойных', 'фигурных', 'скобках', '}', '}', '{', 'Здесь', 'больше', 'текста', 'в', 'одинарных', 'фигурных', 'скобках', '.', '}']
Code language: JavaScript (javascript)

Нормализация

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

Нормализация текста может подразумевать выполнение ряда задач, но для нашей системы мы будем подходить к нормализации в три отдельных этапа: (1) деривация, (2) лемматизация и (3) все остальное.

Помните, что после токенизации мы работаем уже не на уровне текста, а на уровне слова. Наши функции нормализации, показанные ниже, отражают это. Имена функций и комментарии дадут понять что делает каждая из них.

def remove_non_ascii(words): """Удаление символов, не являющихся символами ASCII, из списка токенизированных слов""" new_words = [] for word in words: new_word = unicodedata.normalize('NFKD', word) print(new_word) new_words.append(new_word) return new_words def to_lowercase(words): """Преобразование всех символов в строчные из списка токенизированных слов""" new_words = [] for word in words: new_word = word.lower() new_words.append(new_word) return new_words def remove_punctuation(words): """Удаление знаков препинания из списка токенизированных слов""" new_words = [] for word in words: new_word = re.sub(r'[^\w\s]', '', word) if new_word != '': new_words.append(new_word) return new_words def replace_numbers(words): """Заменить все числа в списке токенизированных слов текстовым представлением""" new_words = [] for word in words: if word.isdigit(): new_word = num2words(word, lang='ru') new_words.append(new_word) else: new_words.append(word) return new_words def remove_stopwords(words): """Удаление стоп-слов из списка токенизированных слов""" new_words = [] for word in words: if word not in stopwords.words('russian'): new_words.append(word) return new_words def stem_words(words): """Исходные слова в списке токенизированных слов""" stemmer = LancasterStemmer() stems = [] for word in words: stem = stemmer.stem(word) stems.append(stem) return stems def lemmatize_verbs(words): """Лемматизировать глаголы в списке токенизированных слов""" lemmatizer = WordNetLemmatizer() lemmas = [] for word in words: lemma = lemmatizer.lemmatize(word, pos='v') lemmas.append(lemma) return lemmas def normalize(words): words = remove_non_ascii(words) words = to_lowercase(words) words = remove_punctuation(words) words = replace_numbers(words) words = remove_stopwords(words) return words words = nltk.word_tokenize(sample) words = normalize(words) print(words)
Code language: PHP (php)

После вызова функции получим:

['заголовок, 'страницыницыцыы, 'жирныиныи, 'текст, 'текст, 'курсивомивомомм, 'это, 'бегугуу, 'бежал, 'бежит, 'перестануттанут, 'бегатьать, 'говорил, 'говориларилалаа, 'говорилирилилии, 'нимимии, 'бегегее, 'побежал, 'говорящемуящему, 'бегунууну, 'находится, 'текст, 'которыи, 'хотим, 'сохранять, 'ивананн, 'сергеигеи, 'александр, 'василии, 'собираютсяаются, 'магазин, 'завтратра, 'утром, 'чтото, 'этимимм, 'предложениемжениемием, 'могугуу, 'делатьать, 'зналалл, 'почемуему, 'мог, 'поужинать, 'ресторане, 'мои, 'любимые, 'кинофраншизыаншизыизы, 'порядку, 'индиана, 'джонс, 'фильмыьмы, 'вселеннои, 'марвелвел, 'звездныедныеыее, 'воины, 'назад, 'будущее, 'гарри, 'поттертер, 'делаи, 'простосто, 'делаи, 'билли, 'знаюаюю, 'делаешь, 'отличныичныиыии, 'домик, 'джононн, 'джеимсимс, 'причинчин, 'делатьать, 'сто, 'причина, 'делатьать, 'одининн, 'причинчин, 'самом, 'делелее, 'нужно, 'купитьить, 'пачки, 'двухухх, 'разныхных, 'магазинах, 'двадцатьцатьтьь, 'сорок, 'однанаа, 'четыреста, 'какието, 'слова, 'внутритри, 'двоиных, 'фигурныхрныхыхх, 'скобках, 'текстаста, 'одинарных, 'фигурныхрныхыхх, 'скобках]
Code language: JSON / JSON with Comments (json)

Вызов функций извлечения и лемминга осуществляется следующим образом:

def stem_and_lemmatize(words): stems = stem_words(words) lemmas = lemmatize_verbs(words) return stems, lemmas stems, lemmas = stem_and_lemmatize(words) print('Исходные лексемы::\n', stems) print('\nРезультат лемматизации:\n', lemmas)
Code language: PHP (php)

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

Исходныедныеыее: ['заголовок', 'страницы', 'жирныи', 'текст', 'текст', 'курсивом', 'это', 'бегу', 'бежал', 'бежит', 'перестанут', 'бегать', 'говорил', 'говорила', 'говорили', 'ними', 'беге', 'побежал', 'говорящему', 'бегуну', 'находится', 'текст', 'которыи', 'хотим', 'сохранять', 'иван', 'сергеи', 'александр', 'василии', 'собираются', 'магазин', 'завтра', 'утром', 'чтото', 'этим', 'предложением', 'могу', 'делать', 'знал', 'почему', 'мог', 'поужинать', 'ресторане', 'мои', 'любимые', 'кинофраншизы', 'порядку', 'индиана', 'джонс', 'фильмы', 'вселеннои', 'марвел', 'звездные', 'воины', 'назад', 'будущее', 'гарри', 'поттер', 'делаи', 'просто', 'делаи', 'билли', 'знаю', 'делаешь', 'отличныи', 'домик', 'джон', 'джеимс', 'причин', 'делать', 'сто один', 'причина', 'делать', 'один миллион', 'причин', 'самом', 'деле', 'нужно', 'купить', 'пачки', 'двух', 'разных', 'магазинах', 'двадцать два', 'сорок пять', 'одна тысяча шестьдесят семь', 'четыреста сорок пять', 'какието', 'слова', 'внутри', 'двоиных', 'фигурных', 'скобках', 'текста', 'одинарных', 'фигурных', 'скобках'] Результат: ['заголовок', 'страницы', 'жирныи', 'текст', 'текст', 'курсивом', 'это', 'бегу', 'бежал', 'бежит', 'перестанут', 'бегать', 'говорил', 'говорила', 'говорили', 'ними', 'беге', 'побежал', 'говорящему', 'бегуну', 'находится', 'текст', 'которыи', 'хотим', 'сохранять', 'иван', 'сергеи', 'александр', 'василии', 'собираются', 'магазин', 'завтра', 'утром', 'чтото', 'этим', 'предложением', 'могу', 'делать', 'знал', 'почему', 'мог', 'поужинать', 'ресторане', 'мои', 'любимые', 'кинофраншизы', 'порядку', 'индиана', 'джонс', 'фильмы', 'вселеннои', 'марвел', 'звездные', 'воины', 'назад', 'будущее', 'гарри', 'поттер', 'делаи', 'просто', 'делаи', 'билли', 'знаю', 'делаешь', 'отличныи', 'домик', 'джон', 'джеимс', 'причин', 'делать', 'сто один', 'причина', 'делать', 'один миллион', 'причин', 'самом', 'деле', 'нужно', 'купить', 'пачки', 'двух', 'разных', 'магазинах', 'двадцать два', 'сорок пять', 'одна тысяча шестьдесят семь', 'четыреста сорок пять', 'какието', 'слова', 'внутри', 'двоиных', 'фигурных', 'скобках', 'текста', 'одинарных', 'фигурных', 'скобках']
Code language: JavaScript (javascript)

И это все! Перед вами руководство по простому процессу предварительной обработки текстовых данных с помощью Python на примере текста. Я рекомендую вам выполнить эти задания на каком-нибудь дополнительном тексте, чтобы проверить результаты.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *