Этапы предварительной обработки текста и универсальный многоразовый конвейер
Описание всех этапов предварительной обработки текста и создание многоразового конвейера предварительной обработки текста.
Прежде чем вводить какие-либо данные в какую-либо модель машинного обучения, они должны быть должным образом предварительно обработаны. Вы, должно быть, слышали пословицу: Garbage in, garbage out
(GIGO). Текст - это особый вид данных, и его нельзя напрямую передать в большинство моделей машинного обучения, поэтому перед тем, как передать его в модель, вы должны каким-то образом извлечь из него числовые характеристики, другими словами vectorize
. Векторизация не является темой этого урока, но главное, что вы должны понять, это то, что GIGO применима и для векторизации, вы можете извлекать качественные характеристики только из качественно предварительно обработанного текста.
Что мы собираемся обсудить:
- Токенизация
- Уборка
- Нормализация
- Лемматизация
- Приготовление на пару
Наконец, мы создадим многоразовый конвейер, который вы сможете использовать в своих приложениях.
Ядро Kaggle: https://www.kaggle.com/balatmak/text-preprocessing-steps-and-universal-pipeline
Предположим, что это пример текста:
An explosion targeting a tourist bus has injured at least 16 people near the Grand Egyptian Museum, next to the pyramids in Giza, security sources say E.U. South African tourists are among the injured. Most of those hurt suffered minor injuries, while three were treated in hospital, N.A.T.O. say. http://localhost:8888/notebooks/Text%20preprocessing.ipynb @nickname of twitter user and his email is [email protected] . A device went off close to the museum fence as the bus was passing on 16/02/2012.
Токенизация
Tokenization
- этап предварительной обработки текста, предполагающий разбиение текста на tokens
(слова, предложения и т. Д.)
Похоже, для этого можно использовать какой-то простой разделитель, но не нужно забывать, что существует множество различных ситуаций, в которых разделители просто не работают. Например, разделитель .
для токенизации в предложения не сработает, если у вас есть сокращения с точками. Поэтому для достижения достаточно хорошего результата нужна более сложная модель. Обычно эта проблема решается с помощью nltk
или spacy
nlp-библиотек.
НЛТК:
from nltk.tokenize import sent_tokenize, word_tokenize nltk_words = word_tokenize(example_text) display(f"Tokenized words: {nltk_words}")
Выход:
Tokenized words: ['An', 'explosion', 'targeting', 'a', 'tourist', 'bus', 'has', 'injured', 'at', 'least', '16', 'people', 'near', 'the', 'Grand', 'Egyptian', 'Museum', ',', 'next', 'to', 'the', 'pyramids', 'in', 'Giza', ',', 'security', 'sources', 'say', 'E.U', '.', 'South', 'African', 'tourists', 'are', 'among', 'the', 'injured', '.', 'Most', 'of', 'those', 'hurt', 'suffered', 'minor', 'injuries', ',', 'while', 'three', 'were', 'treated', 'in', 'hospital', ',', 'N.A.T.O', '.', 'say', '.', 'http', ':', '//localhost:8888/notebooks/Text', '%', '20preprocessing.ipynb', '@', 'nickname', 'of', 'twitter', 'user', 'and', 'his', 'email', 'is', 'email', '@', 'gmail.com', '.', 'A', 'device', 'went', 'off', 'close', 'to', 'the', 'museum', 'fence', 'as', 'the', 'bus', 'was', 'passing', 'on', '16/02/2012', '.']
Просторный:
import spacy import en_core_web_sm nlp = en_core_web_sm.load() doc = nlp(example_text) spacy_words = [token.text for token in doc] display(f"Tokenized words: {spacy_words}")
Выход:
Tokenized words: ['\\n', 'An', 'explosion', 'targeting', 'a', 'tourist', 'bus', 'has', 'injured', 'at', 'least', '16', 'people', 'near', 'the', 'Grand', 'Egyptian', 'Museum', ',', '\\n', 'next', 'to', 'the', 'pyramids', 'in', 'Giza', ',', 'security', 'sources', 'say', 'E.U.', '\\n\\n', 'South', 'African', 'tourists', 'are', 'among', 'the', 'injured', '.', 'Most', 'of', 'those', 'hurt', 'suffered', 'minor', 'injuries', ',', '\\n', 'while', 'three', 'were', 'treated', 'in', 'hospital', ',', 'N.A.T.O.', 'say', '.', '\\n\\n', 'http://localhost:8888/notebooks', '/', 'Text%20preprocessing.ipynb', '\\n\\n', '@nickname', 'of', 'twitter', 'user', 'and', 'his', 'email', 'is', '[email protected]', '.', '\\n\\n', 'A', 'device', 'went', 'off', 'close', 'to', 'the', 'museum', 'fence', 'as', 'the', 'bus', 'was', 'passing', 'on', '16/02/2012', '.', '\\n']
В расширенной токенизации вывода, но не в nltk:
{'E.U.', '\\n', 'Text%20preprocessing.ipynb', '[email protected]', '\\n\\n', 'N.A.T.O.', 'http://localhost:8888/notebooks', '@nickname', '/'}
В nltk, но не в просторном:
{'nickname', '//localhost:8888/notebooks/Text', 'N.A.T.O', ':', '@', 'gmail.com', 'E.U', 'http', '20preprocessing.ipynb', '%'}
Мы видим, что spacy
токенизировал некоторые странные вещи, такие как \n
, \n\n
, но мог обрабатывать URL-адреса, электронные письма и упоминания, подобные Twitter. Кроме того, мы видим, что nltk
размеченных сокращений без последних .
Уборка
Cleaning
этот шаг предполагает удаление всего нежелательного содержимого.
Удаление знаков препинания
Punctuation removal
может быть хорошим шагом, когда пунктуация не приносит дополнительной ценности для векторизации текста. Удаление знаков препинания лучше производить после этапа токенизации, так как это может вызвать нежелательные эффекты. Хороший выбор для TF-IDF
, Count
, Binary
векторизации.
Предположим, что этот текст для этого шага:
@nickname of twitter user, and his email is [email protected] .
Перед токенизацией:
text_without_punct = text_with_punct.translate(str.maketrans('', '', string.punctuation)) display(f"Text without punctuation: {text_without_punct}")
Выход:
Text without punctuation: nickname of twitter user and his email is emailgmailcom
Здесь вы можете увидеть, что важные символы для правильной токенизации были удалены. Теперь электронная почта не может быть правильно определена. Как вы могли упомянуть на шаге Tokenization
, символы пунктуации анализировались как отдельные токены, поэтому лучше было бы сначала токенизировать, а затем удалять символы пунктуации.
import spacy import en_core_web_sm nlp = en_core_web_sm.load() doc = nlp(text_with_punct) tokens = [t.text for t in doc] # python based removal tokens_without_punct_python = [t for t in tokens if t not in string.punctuation] display(f"Python based removal: {tokens_without_punct_python}") # spacy based removal tokens_without_punct_spacy = [t.text for t in doc if t.pos_ != 'PUNCT'] display(f"Spacy based removal: {tokens_without_punct_spacy}")
Результат удаления на основе Python:
['@nickname', 'of', 'twitter', 'user', 'and', 'his', 'email', 'is', '[email protected]']
Удаление на основе Spacy:
['of', 'twitter', 'user', 'and', 'his', 'email', 'is', '[email protected]']
Здесь вы видите, что удаление python-based
сработало даже лучше, чем spacy, потому что spacy пометил @nicname
как PUNCT
часть речи.
Удаление стоп-слов
Stop words
обычно относится к наиболее употребительным словам в языке, которые обычно не имеют дополнительного значения. Не существует единого универсального списка стоп-слов, используемых всеми инструментами nlp, потому что этот термин имеет очень нечеткое определение. Хотя практика показала, что этот шаг обязателен при подготовке текста к индексации, но может оказаться сложным для целей классификации текста.
Количество просторных стоп-слов: 312
Количество стоп-слов NLTK: 179
Предположим, что этот текст для этого шага:
This movie is just not good enough
Просторный:
import spacy import en_core_web_sm nlp = en_core_web_sm.load() text_without_stop_words = [t.text for t in nlp(text) if not t.is_stop] display(f"Spacy text without stop words: {text_without_stop_words}")
Объемный текст без стоп-слов:
['movie', 'good']
НЛТК:
import nltk nltk_stop_words = nltk.corpus.stopwords.words('english') text_without_stop_words = [t for t in word_tokenize(text) if t not in nltk_stop_words] display(f"nltk text without stop words: {text_without_stop_words}")
Текст NLTK без стоп-слов:
['This', 'movie', 'good', 'enough']
Здесь вы видите, что nltk и spacy имеют разный размер словаря, поэтому результаты фильтрации разные. Но главное, что я хочу подчеркнуть, это то, что слово not
было отфильтровано, что в большинстве случаев будет нормально, но в случае, когда вы захотите определить полярность этого предложения, not
принесет дополнительный смысл.
Для таких случаев вы можете установить стоп-слова, которые можно игнорировать, в библиотеке spacy. В случае nltk вы просто удалите или добавите пользовательские слова в nltk_stop_words
, это просто список.
import en_core_web_sm nlp = en_core_web_sm.load() customize_stop_words = [ 'not' ] for w in customize_stop_words: nlp.vocab[w].is_stop = False text_without_stop_words = [t.text for t in nlp(text) if not t.is_stop] display(f"Spacy text without updated stop words: {text_without_stop_words}")
Объемный текст без обновленных стоп-слов:
['movie', 'not', 'good']
Нормализация
Как и любые данные, текст требует нормализации. В случае текста это:
- Преобразование дат в текст
- Цифры в текст
- Знаки валюты / процента в тексте
- Расширение сокращений (зависит от содержания) NLP - Natural Language Processing, нейролингвистическое программирование, нелинейное программирование
- Исправление орфографических ошибок
Подводя итог, нормализация - это преобразование любой нетекстовой информации в текстовый эквивалент.
Для этого существует отличная библиотека - normalize. Я покажу вам использование этой библиотеки из README. Эта библиотека основана на nltk
пакете, поэтому ожидает nltk
токенов слов.
Предположим, что этот текст для этого шага:
On the 13 Feb. 2007, Theresa May announced on MTV news that the rate of childhod obesity had risen from 7.3-9.6% in just 3 years , costing the N.A.T.O £20m
Код:
from normalise import normalise user_abbr = { "N.A.T.O": "North Atlantic Treaty Organization" } normalized_tokens = normalise(word_tokenize(text), user_abbrevs=user_abbr, verbose=False) display(f"Normalized text: {' '.join(normalized_tokens)}")
Выход:
On the thirteenth of February two thousand and seven , Theresa May announced on M T V news that the rate of childhood obesity had risen from seven point three to nine point six % in just three years , costing the North Atlantic Treaty Organization twenty million pounds
Хуже всего в этой библиотеке то, что сейчас вы не можете отключить некоторые модули, такие как расширение аббревиатуры, и это вызывает такие вещи, как MTV
- ›M T V
. Но я уже добавил соответствующую проблему в этот репозиторий, возможно, она будет исправлена через некоторое время.
Лемматизация и пропаривание
Stemming
- это процесс уменьшения перегиба в словах до их корневых форм, например, отображение группы слов на одну основу, даже если сама основа не является допустимым словом в Языке.
Lemmatization
, в отличие от Stemming, уменьшает изменяемые слова должным образом, гарантируя, что корневое слово принадлежит языку. В лемматизации корневое слово называется леммой. Лемма (леммы или леммы множественного числа) - это каноническая форма, словарная форма или форма цитирования набора слов.
Предположим, что этот текст для этого шага:
On the thirteenth of February two thousand and seven , Theresa May announced on M T V news that the rate of childhood obesity had risen from seven point three to nine point six % in just three years , costing the North Atlantic Treaty Organization twenty million pounds
Стеммер НЛТК:
import numpy as np from nltk.stem import PorterStemmer from nltk.tokenize import word_tokenize tokens = word_tokenize(text) porter=PorterStemmer() # vectorizing function to able to call on list of tokens stem_words = np.vectorize(porter.stem) stemed_text = ' '.join(stem_words(tokens)) display(f"Stemed text: {stemed_text}")
Выделенный текст:
On the thirteenth of februari two thousand and seven , theresa may announc on M T V news that the rate of childhood obes had risen from seven point three to nine point six % in just three year , cost the north atlant treati organ twenti million pound
Лемматизация НЛТК:
import numpy as np from nltk.stem import WordNetLemmatizer from nltk.tokenize import word_tokenize tokens = word_tokenize(text) wordnet_lemmatizer = WordNetLemmatizer() # vectorizing function to able to call on list of tokens lemmatize_words = np.vectorize(wordnet_lemmatizer.lemmatize) lemmatized_text = ' '.join(lemmatize_words(tokens)) display(f"nltk lemmatized text: {lemmatized_text}")
Лемматизированный текст NLTK:
On the thirteenth of February two thousand and seven , Theresa May announced on M T V news that the rate of childhood obesity had risen from seven point three to nine point six % in just three year , costing the North Atlantic Treaty Organization twenty million pound
Пространственная лемматизация:
import en_core_web_sm nlp = en_core_web_sm.load() lemmas = [t.lemma_ for t in nlp(text)] display(f"Spacy lemmatized text: {' '.join(lemmas)}")
Просторный лемматизированный текст:
On the thirteenth of February two thousand and seven , Theresa May announce on M T v news that the rate of childhood obesity have rise from seven point three to nine point six % in just three year , cost the North Atlantic Treaty Organization twenty million pound
Мы видим, что spacy
лемматизируется намного лучше, чем nltk, один из примеров risen
- ›rise
, только spacy
справился с этим.
Многоразовый трубопровод
А теперь моя любимая часть! Мы собираемся создать многоразовый конвейер, который вы могли бы использовать в любом из своих проектов.
import numpy as np import multiprocessing as mp import string import spacy import en_core_web_sm from nltk.tokenize import word_tokenize from sklearn.base import TransformerMixin, BaseEstimator from normalise import normalise nlp = en_core_web_sm.load() class TextPreprocessor(BaseEstimator, TransformerMixin): def __init__(self, variety="BrE", user_abbrevs={}, n_jobs=1): """ Text preprocessing transformer includes steps: 1. Text normalization 2. Punctuation removal 3. Stop words removal 4. Lemmatization variety - format of date (AmE - american type, BrE - british format) user_abbrevs - dict of user abbreviations mappings (from normalise package) n_jobs - parallel jobs to run """ self.variety = variety self.user_abbrevs = user_abbrevs self.n_jobs = n_jobs def fit(self, X, y=None): return self def transform(self, X, *_): X_copy = X.copy() partitions = 1 cores = mp.cpu_count() if self.n_jobs <= -1: partitions = cores elif self.n_jobs <= 0: return X_copy.apply(self._preprocess_text) else: partitions = min(self.n_jobs, cores) data_split = np.array_split(X_copy, partitions) pool = mp.Pool(cores) data = pd.concat(pool.map(self._preprocess_part, data_split)) pool.close() pool.join() return data def _preprocess_part(self, part): return part.apply(self._preprocess_text) def _preprocess_text(self, text): normalized_text = self._normalize(text) doc = nlp(normalized_text) removed_punct = self._remove_punct(doc) removed_stop_words = self._remove_stop_words(removed_punct) return self._lemmatize(removed_stop_words) def _normalize(self, text): # some issues in normalise package try: return ' '.join(normalise(text, variety=self.variety, user_abbrevs=self.user_abbrevs, verbose=False)) except: return text def _remove_punct(self, doc): return [t for t in doc if t.text not in string.punctuation] def _remove_stop_words(self, doc): return [t for t in doc if not t.is_stop] def _lemmatize(self, doc): return ' '.join([t.lemma_ for t in doc])
Этот код можно использовать в конвейере sklearn.
Измеренная производительность: 2225 текстов было обработано на 4 процессах за 22 минуты. Даже близко к тому, чтобы быть быстрым! Это вызывает часть нормализации, библиотека недостаточно оптимизирована, но дает довольно интересные результаты и может принести дополнительную ценность для дальнейшей векторизации, поэтому вам решать, использовать ее или нет.
Надеюсь, вам понравился этот пост, и я с нетерпением жду вашего отзыва!
Ядро Kaggle: https://www.kaggle.com/balatmak/text-preprocessing-steps-and-universal-pipeline