Авторы: Парин Джавери, Рия Джоши

В предыдущей статье мы дали обзор того, что такое вложения и их широкие категории — разреженные и плотные. В этой статье мы сосредоточимся на том, как можно решать задачи НЛП с помощью разреженных вложений (TF-IDF, Bag of Words, BM25 и т. д.) и классических алгоритмов машинного обучения.

Предварительные условия: основы машинного обучения

Чтобы объяснить поток, мы возьмем пример задачи классификации текста.

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

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

Давайте сначала подробно рассмотрим каждый из шагов, приведенных в приведенной выше блок-схеме.

Набор данных

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

Набор данных, на который мы будем ссылаться, — это популярный набор данных классификации текстов: IMDB Movie Reviews Dataset — двоичный набор данных анализа настроений, состоящий из обзоров фильмов.
Этот набор данных содержит 50 000 обзоров фильмов, которые разделены на 25 000 наборов поездов и тестов. Распределение положительных и отрицательных меток в каждом сплите поезд/тест сбалансировано.
Вы можете скачать набор данных ЗДЕСЬ. Набор данных заархивирован в формате .tar.gz, и каждый образец текста хранится в файле .txt в каталогах train/pos, train/neg, test/pos и test/neg.
Давайте загрузим этот набор данных и создадим два списка — train_data и test_data:

!tar -xzvf aclImdb_v1.tar.gz

import glob
train_pos_files = glob.glob('aclImdb/train/pos/*.txt')
train_neg_files = glob.glob('aclImdb/train/neg/*.txt')
test_pos_files = glob.glob('aclImdb/test/pos/*.txt')
test_neg_files = glob.glob('aclImdb/test/neg/*.txt')

train_data = []
test_data = []
for file in train_pos_files:
  f = open(file)
  text = f.read()
  train_data.append([text, 'positive'])

for file in train_neg_files:
  f = open(file)
  text = f.read()
  train_data.append([text, 'negative'])

for file in test_pos_files:
  f = open(file)
  text = f.read()
  test_data.append([text, 'positive'])

for file in test_neg_files:
  f = open(file)
  text = f.read()
  test_data.append([text, 'negative'])

print(len(train_data))
print(len(test_data))
Output: 
25000
25000

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

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

  • Работа с отсутствующими или нулевыми значениями — вменение нулевых значений средним/медианным или другим показателям или полное их удаление.
  • Кодирование меток/горячее кодирование категории - Преобразование текстовых столбцов в числа. Для нашего варианта использования текстовой классификации мы применим кодирование меток к нашим обучающим и тестовым данным, т. е. положительные метки будут помечены как 1, а отрицательные метки будут помечены как 0.
# Label Encoding

for data in train_data:
  if data[1] == 'positive':
    data[1] = 1
  elif data[1] == 'negative':
    data[1] = 0

for data in test_data:
  if data[1] == 'positive':
    data[1] = 1
  elif data[1] == 'negative':
    data[1] = 0

После выполнения общих шагов предварительной обработки применяются специальные методы предварительной обработки текста для каждого образца:

a) Токенизация.
Токенизация включает в себя разделение образца текста на слова, n-граммы слов/символов или другие значимые сегменты. , известные как токены.
Пример:
-
Предложение: Эта статья потрясающая
- Словарные токены: [Это, статья, потрясающая]

Определение:n-граммы
n-граммы — это группа изn непрерывных элементов в текстовом образце.
Возьмем тот же пример предложения, что и выше —
word 3-gram (
n=3):
(‹s›, ‹ s›, Это) (‹s›, Это, Статья) (Это, Статья, есть) (Статья, есть, изумительно!) )
символ 3-грамм (
n=3):
(‹s›, ‹s›, T) (‹s›, T , H) (T, H, I) …..
ну вы поняли😜

Для классификации текста, чтобы применить другие шаги предварительной обработки данных к нашим данным, мы сначала разделим наши выборки только на слова. Для других вариантов использования NLP вы можете предпочесть n-граммы или другие методы токенизации.

Мы будем использовать библиотеку nltk для токенизации слов.

import nltk
nltk.download('punkt')
from nltk.tokenize import word_tokenize

for data in train_data:
  data[0] = word_tokenize(data[0])

for data in test_data:
  data[0] = word_tokenize(data[0])

б) Удаление стоп-слов.
Стоп-слова – это слова, которые очень часто встречаются в любом конкретном языке и не имеют особого смысла.
Некоторыми стандартными примерами в английском языке являются артикли и союзы, такие как a, an, and, but, the ии т. д. Вы можете распечатать стоп-слова, используя этот фрагмент кода:

import nltk
from nltk.corpus import stopwords
 
nltk.download('stopwords')
print(stopwords.words('english'))
Output:
['i', 'me', 'my', 'myself', 'we', 'our', 'ours', 'ourselves', 'you', "you're", "you've", "you'll", "you'd", 'your', 'yours', 'yourself', 'yourselves', 'he', 'him', 'his', 'himself', 'she', "she's", 'her', 'hers', 'herself', 'it', "it's", 'its', 'itself', 'they', 'them', 'their', 'theirs', 'themselves', 'what', 'which', 'who', 'whom', 'this', 'that', "that'll", 'these', 'those', 'am', 'is', 'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had', 'having', 'do', 'does', 'did', 'doing', 'a', 'an', 'the', 'and', 'but', 'if', 'or', 'because', 'as', 'until', 'while', 'of', 'at', 'by', 'for', 'with', 'about', 'against', 'between', 'into', 'through', 'during', 'before', 'after', 'above', 'below', 'to', 'from', 'up', 'down', 'in', 'out', 'on', 'off', 'over', 'under', 'again', 'further', 'then', 'once', 'here', 'there', 'when', 'where', 'why', 'how', 'all', 'any', 'both', 'each', 'few', 'more', 'most', 'other', 'some', 'such', 'no', 'nor', 'not', 'only', 'own', 'same', 'so', 'than', 'too', 'very', 's', 't', 'can', 'will', 'just', 'don', "don't", 'should', "should've", 'now', 'd', 'll', 'm', 'o', 're', 've', 'y', 'ain', 'aren', "aren't", 'couldn', "couldn't", 'didn', "didn't", 'doesn', "doesn't", 'hadn', "hadn't", 'hasn', "hasn't", 'haven', "haven't", 'isn', "isn't", 'ma', 'mightn', "mightn't", 'mustn', "mustn't", 'needn', "needn't", 'shan', "shan't", 'shouldn', "shouldn't", 'wasn', "wasn't", 'weren', "weren't", 'won', "won't", 'wouldn', "wouldn't"]

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

import nltk
from nltk.corpus import stopwords

# Removing Stopwords
stop_words = stopwords.words('english')
for i, data in enumerate(train_data):
  filtered_sentence = [word_token for word_token in data[0] if not word_token.lower() in stop_words]
  train_data[i] = [filtered_sentence, data[1]]

for i, data in enumerate(test_data):
  filtered_sentence = [word_token for word_token in data[0] if not word_token.lower() in stop_words]
  test_data[i] = [filtered_sentence, data[1]]

c) Удаление знаков препинания:
На следующем этапе, когда мы хотим извлечь функции, знаки препинания не содержат ценной информации. Кроме того, мы хотим, чтобы разреженные вложения «книги» и «книги» были одинаковыми.
Давайте удалим знаки препинания для нашего варианта использования:

# Removing Punctuations
import string
for data in train_data:
  for punctuation in string.punctuation:
      data[0] = [word_token.replace(punctuation, '') for word_token in data[0]]

for data in test_data:
  for punctuation in string.punctuation:
      data[0] = [word_token.replace(punctuation, '') for word_token in data[0]]

c) Основа:
Основа включает сокращение слова до его основной или корневой формы путем удаления нескольких символов в конце слова.
Пример:
 –
Stemmer("running") → "run"
Мы видим, как выделение корней помогает уменьшить размер словаря, а также неявно гарантирует, что различные формы корневого слова имеют одинаковое вложение (например, «бег» и «бег»)
Существуют определенные проблемы с определением основы, поскольку это может привести к тому, что слово не существует в словаре.
Пример:
 –
Стиммер ("полностью") → "завершено"
Стемминг работает на пословной основе.

Вот как вы можете сделать вывод с помощью библиотеки nltk:

import nltk
from nltk.stem import PorterStemmer

stemmer = PorterStemmer()
print(stemmer.stem('running'))
Output:
run

Для классификации текста я выбираю лемматизацию (см. ниже).

d) Лемматизация:
Лемматизация достигает той же цели, что и поиск корней, но дополнительно учитывает контекст этого слова в наборе данных/корпусе, а затем приступает к приведению его к базовой форме или лемма.
Лемматизация также определяет Часть речи слова, просматривая окружающий текст, что в дальнейшем помогает ему решить, как и что делать. сократить слово до.
Этот метод выполняет сокращение слов гораздо более информативным способом по сравнению со стеммингом и, следовательно, во много раз предпочтительнее стемминга.

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

Определение: Часть речи (POS)
Процесс классификации слова по его грамматической функции/роли на основе окружающих его слов (контекст!). В английском языке есть 8 частей речи
Пример POS-тегов:
-Предложение: Мне нравится читать информативные статьи.
-POS-теги: ( I, местоимение) (нравится, глагол) (чтение, глагол) (информативное, прилагательное) (артикли, существительное)

Давайте продолжим и посмотрим, как мы применим лемматизацию к нашему набору данных в коде:

from nltk.stem import WordNetLemmatizer
lemmatizer = WordNetLemmatizer()
nltk.download('wordnet')

for data in train_data:
    data[0] = [lemmatizer.lemmatize(word_token) for word_token in data[0]]

for data in test_data:
    data[0] = [lemmatizer.lemmatize(word_token) for word_token in data[0]]

print(train_data[0][0])
Output:
['Kids', '', 'whatever', 'age', '', 'want', 'know', 'parent', '', 'sex', 'life', '', 'grownup', 'child', 'often', 'seriously', 'baffled', 'disconcerted', 'evidence', 'aging', 'parent', 'posse', 'active', 'libido', '', 'Lastly', '', 'many', 'moviegoer', 'uncomfortable', 'watching', 'dowdy', '', 'frumpy', 'widow', 'would', 'pas', 'unnoticed', 'almost', 'anywhere', 'discover', 'aching', 'capacity', 'need', 'raw', 'passion', 'handsome', 'man', 'half', 'age', '', 'br', '', '', '', 'br', '', '', '', 'Mother', '', 'provocative', 'look', 'scarcely', 'filmed', 'reality', '', 'woman', 'nt', 'ready', 'stay', 'home', '', 'watch', '', 'telly', '', '', 'vegetate', 'husband', 'nearly', 'three', 'decade', '', 'controlling', '', 'dominating', 'chap', '', 'pack', 'massive', 'heart', 'attack', '', 'br', '', '', '', 'br', '', '', 'May', '', 'Anne', 'Reid', '', 'husband', 'two', 'child', '', 'dysfunctional', 'way', '', 'male', 'son', 'life', 'beautiful', 'wife', 'may', 'well', 'driving', 'Bankruptcy', 'Court', 'extravagant', 'commercial', 'venture', '', 'Paula', '', 'Cathryn', 'Bradshaw', '', '', 'teacher', 'aspiration', 'succeeding', 'writer', '', 's', 'attractive', '', 'pretty', '', 'seems', 'close', 'relationship', 'mum', '', 'first', '', 'br', '', '', '', 'br', '', '', 'Back', 'house', 'burying', 'husband', '', 'May', 'determines', 'stay', '', 'Rejecting', 'typical', 'widowhood', 'legacy', 'boring', 'day', 'adventure', '', 'go', 'stay', 'Paula', 'young', 'son', '', 'Paula', 's', 'boyfriend', '', 'Darren', '', 'Daniel', 'Craig', '', '', 'ruggedly', 'handsome', 'contractor', 'seems', 'taking', 'awfully', 'long', 'time', 'complete', 'addition', 'May', 's', 'son', 's', 'house', '', 'May', 'quite', 'taken', 'harddrinking', '', 'cokesniffing', 'Darren', 'whose', 'treatment', 'Paula', 'ought', 'alerted', 'May', '', 'sure', '', 'Fellow', 'Royal', 'Academy', 'Cads', '', 'br', '', '', '', 'br', '', '', 'follows', 'torrid', 'affair', 'Darren', 'besotted', 'bubblingly', 'alive', '', 'dare', 'say', 'reborn', '', '', 'widow', '', 'love', 'scene', 'graphic', 'take', 'second', 'place', 'amateur', 'artist', 'May', 's', 'pen', 'ink', 'sketch', 'tryst', 'play', 'role', 'enfolding', 'drama', '', 'debacle', '', 'take', 'pick', '', '', '', 'br', '', '', '', 'br', '', '', 'theater', 'Manhattan', 'packed', 'today', 's', 'early', 'afternoon', 'showing', 'well', 'half', 'audience', 'range', 'May', 's', 'age', '', 'shocked', 'disturbed', 'see', 'disporting', 'erotic', 'abandon', 'arm', 'much', 'younger', 'man', 'understatement', '', '', 'br', '', '', '', 'br', '', '', 'blindingly', 'honest', 'look', 'older', 'woman', 's', 'awakened', 'passion', 'decade', 'dutifully', 'obeying', 'husband', 's', 'desire', 'stay', 'home', 'raise', 'kid', '', 'also', 'mention', 'nt', 'like', 'friendswhat', 'guy', '', 'surface', 'number', 'issue', '', 'May', 's', 'dalliance', 'Darren', 'nt', 'constitute', 'incest', '', 'real', 'psychological', 'dimension', '', 'issue', '', 'mother', 'bedding', 'daughter', 's', 'lover', '', 'Paula', 'nt', 'made', 'stoutest', 'stuff', 'begin', '', 'affair', '', 'disclosed', '', 'allows', 'peeling', 'open', 'motherdaughter', 'relationship', '', 'Paula', 's', 'viewpoint', '', 'left', 'something', 'desired', '', 'Ms', 'Bradshaw', 'excellent', 'role', 'daughter', 'want', 'mother', 's', 'support', 'well', 'loveshe', 'nt', 'dealt', 'terrible', 'hand', 'life', 'nt', 'bed', 'rose', 'either', '', 'br', '', '', '', 'br', '', '', 'May', 'strong', 'resolve', 'acknowledge', 'sexuality', 'expect', '', 'indeed', 'demand', '', 'future', 'happiness', '', 'also', 'inescapably', 'vulnerable', '', 's', 'fishing', 'uncharted', 'emotional', 'water', '', 'control', 'relationship', 'Darren', 'difficult', 'issue', 'understand', '', 'much', 'le', 'resolve', '', 'sixty', '', 's', 'still', 'work', 'progress', '', 'br', '', '', '', 'br', '', '', '', 'Something', 's', 'Got', 'ta', 'Give', '', 'recently', 'showcased', 'mature', 'sexuality', 'amusingly', 'antiseptic', 'way', 'assuring', 'viewer', 'would', 'discomfited', '', 's', 'Jack', 'Nicholson', 'always', 'beautiful', 'Diane', 'Keaton', 'cavorting', 'world', 'rich', '', 'insure', 'serious', 'psychosocial', 'issue', 'explored', '', 'Keaton', 's', 'young', 'girlfriend', '', 'Amanda', 'Peet', '', 'daughter', 'Keaton', '', 'blesses', 'match', 'insures', 'audience', 'know', 'old', '', 'er', '', 'wouldbe', 'lover', 'never', 'hopped', 'sack', '', 'br', '', '', '', 'br', '', '', 'easy', '', 'Anne', 'Reid', 's', 'inspired', 'performance', 'force', 'discomfort', 'drawing', 'respect', 'others', '', 'naked', 'body', 'burst', 'sexuality', 'appears', 'absurd', 'object', 'physical', 'attraction', 'others', '', 'comment', 'audience', 'member', 'leaving', 'today', 'reflected', 'view', '', '', '', 'br', '', '', '', 'br', '', '', 'Kudos', 'director', 'Roger', 'Michell', 'tackling', 'fascinating', 'story', 'verve', 'empathy', '', 'br', '', '', '', 'br', '', '', '910', '']

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

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

Извлечение признаков

Описание

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

Угадай, что?? В НЛП извлечение признаков — это просто преобразование образцов текста в разреженные вложения! Мы выполняем TF-IDF или Bag-Of-Words для всех образцов в наборе данных.

В дополнение к преобразованию образца текста в разреженные вложения ИЛИ входные функции, мы также можем «спроектировать» функции. Примерами инженерных функций могут быть: «содержит числовые значения», «содержит символы» и т. д.

Давайте посмотрим, как мы будем готовить матрицу признаков для классификации текста.

# Convert all the tokens into a string again
#      a) Remove single character tokens
#      b) Add only one space between each token

train_labels = []
train_samples = []
for data in train_data:
  sentence = ""
  for word_token in data[0]:
    if len(word_token) > 1:
      sentence += word_token + " "
  sentence.strip()
  train_samples.append(sentence)
  train_labels.append(data[1])

test_labels = []
test_samples = []
for data in test_data:
  sentence = ""
  for word_token in data[0]:
    if len(word_token) > 1:
      sentence += word_token + " "
  sentence.strip()
  test_samples.append(sentence)
  test_labels.append(data[1])

# Generating Feature Matrix
# Using TfidfVectorizer from Scikit-Learn library
from sklearn.feature_extraction.text import TfidfVectorizer
vectorizer = TfidfVectorizer()

# fit_transform() function is used on train set
X_train = vectorizer.fit_transform(train_samples)

# transform() function is used on test set, as that will use the same
# vocabulary built on train set
X_test = vectorizer.transform(test_samples)

print(X_train.shape)
print(X_test.shape)
print(X_train[0].toarray())
Output:
(25000, 90235)
(25000, 90235)
[[0. 0. 0. ... 0. 0. 0.]]

Здесь мы видим, что N=25000, а размер словаря равен 90 235. Вы можете видеть, что большинство значений равны нулю, следовательно, вектор является «разреженным».

Поздравляем!!! Вы только что извлекли функции, которые можно предоставить модели машинного обучения для обучения.

Примечание. Важно отметить, что TfidfVectorizer() применяет норму L2 после вычисления оценок TF-IDF для каждого документа.

Моделирование и прогнозы:

Это то, что большинство из вас уже знают. Вы можете использовать любую модель машинного обучения после того, как вы построили матрицу функций — деревья решений, наивный байесовский алгоритм, случайный лес, SVM, логистическая регрессия и т. д.… Вы называете это!

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

В нашем случае мы будем использовать SVM:

from sklearn.svm import SVC
classifier = SVC(gamma='auto')
classifier.fit(X_train, train_labels)

Это займет некоторое время. После завершения обучения давайте оценим производительность нашей модели.

# Running predictions on Test Set
test_preds = classifier.predict(X_test)

# Generating Classification Report
from sklearn.metrics import classification_report
print(classification_report(test_labels, test_preds, target_names=['Positive', 'Negative']))

Точность составляет 62%, а макросредняя оценка F1 — 56%.
Если мы попробуем разные модели и предобработаем данные, то сможем получить лучшие результаты.

Поздравляем!!! Мы создали наш первый классификатор НЛП (многое еще впереди….)!! В этой статье мы показали шаги по созданию классификатора текста. Но эти методы сегодня не очень популярны. Мы добились огромного прогресса в понимании текста и его значения, создав надежные плотные вложения. Мы подробно обсудим эти методы и модели в следующих статьях.

В следующей статье мы поговорим о первой языковой модели и ее нейронной преемнице. Эти языковые модели были первыми генеративными моделями в НЛП.