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

Часть 1: Исследование данных — вы здесь!

Часть 2: Начало разработки и выбора функций

Чем мы будем заниматься в этом разделе

  • Обзор задачи
  • Импорт данных в Python
  • Исследовательский анализ данных (EDA)

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

Почему мы заботимся?

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

Давайте начнем!

Я буду использовать комбинацию замечательных Jupyter Lab и Plotly Express, простую в использовании оболочку для удивительно удобного пакета визуализации Plotly.

# Pandas for data manipulation
import pandas as pd
# Plotly Express for interactive charting inside jupyter lab
import plotly.express as px

Plotly Express имеет встроенную поддержку автономного построения диаграмм внутри Jupyter. Использование vanilla Plotly для достижения той же цели требовало множества других пакетов и зависимостей.

Импорт данных твитов

Набор данных Kaggle — это предварительно обработанная версия открытого набора данных из Data for Everyone. Kaggle удобно разделил твиты на учебные и тестовые файлы .csv. Файл training.csv будет использоваться для построения наших моделей, а файл test.csv будет использоваться для оценки моделей в таблице лидеров Kaggle.

# Read in train.csv and take a look at the first few rows
train_df = pd.read_csv("data/train.csv")
train_df.head()

Не считая столбца id, у нас есть только три функции и наша двоичная цель.

  • id: уникальный идентификатор для каждого твита.
  • ключевое слово. Одно ключевое слово из твита. Это может быть пустым.
  • местоположение. Местоположение твита. Это поле также может быть пустым.
  • text: необработанный текст твита.
  • Цель: 1 для связанных со стихийным бедствием и 0 для не стихийных бедствий.

Высокоуровневый взгляд на наши данные

train_df.info()

Мы видим, что заполнено более 99% keyword, что является хорошим знаком, если мы хотим попробовать использовать его в качестве функции прогнозирования. Однако у нас отсутствуют значения примерно для трети функции location. Мы также видим, что мы не пропускаем ни одного твита или цели.

Метод DataFrame.describe() от Panda предоставляет простой и элегантный способ получения базовой исследовательской статистики по фрейму данных. Это особенно полезно при работе с числовыми функциями (которых у нас сейчас нет).

По умолчанию метод будет игнорировать нечисловые столбцы, такие как text, но мы можем заставить метод включать эти столбцы, включив параметр include='all':

train_df.describe(include='all')

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

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

Проверьте дисбаланс классов

Классификация может быть намного сложнее, когда метка предсказания встречается редко и называется несбалансированностью классов. Мы быстро проверим наш столбец target, чтобы увидеть, нужно ли нам выполнять какое-либо снижение дисбаланса.

# Instantiate histogram using plotly express
fig = px.histogram(train_df, 
                   x = "target",
                   width = 500,
                   height = 500)
fig.update_layout(xaxis_type='category',
                  title_text='Class distribution')
fig.show()

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

Всегда не забывайте проверять дисбаланс классов!

Отсутствующие данные

Мы сделали быструю проверку на раннем этапе, запустив train_df.info(), и увидели, что в keyword и location было некоторое количество отсутствующих данных, при этом последний демонстрирует гораздо более высокий уровень пустых значений.

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

  • Заполнить недостающие данные
  • Отбросьте строки, в которых отсутствуют данные
  • Удалить столбец функций

Чтобы определиться со стратегией, мы более подробно рассмотрим данные keyword и location.

"Keyword" разведка

Метод Series.value_counts() — еще один отличный инструмент Pandas для проверки наиболее и наименее распространенных значений в столбце.

# Value counts
train_df[‘keyword’].value_counts(dropna=False)

Панды Советы!

Помните, что выбор одного столбца нашего фрейма данных возвращает серию Pandas. Метод .value_counts() работает только с сериями.

Параметр dropna=False подсчитывает пропущенные значения. По умолчанию метод игнорирует их.

Интересное мы видим даже из 10 записей:

  • Мы сохранили наши пропущенные значения и видим, что это наше наиболее распространенное «значение» для столбца.
  • Строка %20 появляется дважды, и неудивительно, что это кодировка ASCII для пробела.
  • Epicentre использует британское написание, что может быть полезной информацией ниже по течению, если мы хотим объединить похожие слова или выполнить другие операции НЛП.

Давайте также посмотрим на наши ключевые слова в алфавитном порядке.

# Sort keywords alphabetically
train_df['keyword'].value_counts(dropna=False).sort_index()

Вероятно, самое интересное, что мы видим, это то, что для wreck, wreckage и wrecked существуют отдельные ключевые слова. Это может дать нам идеи о том, как комбинировать термины ключевых слов, чтобы еще больше уменьшить кардинальность столбца.

Интерактивная гистограмма ключевых слов

Если вы установили Plotly Express, запуск этого блока кода создаст интерактивную диаграмму, показывающую все ключевые слова от наименее распространенных до наиболее распространенных.

# Create new dataframe with our count data
train_keywords_bar_df = train_df['keyword'].value_counts().reset_index()
train_keywords_bar_df.columns = ['keyword', 'count']
# Bar chart
fig = px.bar(train_keywords_bar_df, 
                x = "keyword",
                y = "count"
)
fig.update_layout(xaxis_type='category',
                  title_text='Keywords')
fig.show()

Совет Панды!

Функция px.bar() очень легко работает с фреймами данных Pandas. Передавая кадр данных, мы можем просто указать функции, какой столбец использовать для оси x. Но запуск train_df['keyword'].value_counts(dropna=False) возвращает серию Pandas. Мы можем преобразовать его в фрейм данных, добавив .reset_index(), который создает новый числовой индекс для каждой строки и перемещает наши ключевые слова в новый столбец с именем «индекс». Мы перезаписываем имена столбцов нового фрейма данных массивом [‘keyword', ‘count’]. Наконец, мы передаем «ключевое слово» в качестве нашего параметра x и «количество» в качестве нашего y.

Интерактивное изучение ключевых слов в собственном блокноте Jupyter может быть проще, чем экспорт и чтение дампа .csv, особенно на этих ранних этапах.

Простое наличие ключевого слова предсказывает цель?

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

# Copy a slice of our dataframe
keyword_target_df = train_df[['keyword', 'target']].copy()
# Add an indicator column that checks to see if a keyword exists
keyword_target_df['keyword_missing'] = keyword_target_df['keyword'].apply(lambda x: pd.isna(x))
# Drop the actual keyword column
keyword_target_df.drop('keyword', axis=1, inplace=True)
# Check our output
keyword_target_df.head()

# Visualize as barcharts
fig = px.histogram(keyword_target_df, x="keyword_missing", color="target", facet_col='target', histnorm='probability density')
fig.update_layout(xaxis_type='category',
                  yaxis_title='%',
                  width = 1000,
                  height = 500,
                  title_text='Keywords Missing vs. Available % by Target Class')
fig.show()

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

Являются ли отдельные ключевые слова прогнозируемыми?

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

# Create a pivot table to count the numbers of classes per unique keyword
keyword_by_target_dist = pd.pivot_table(
train_df[['id', 'keyword', 'target']], 
index='keyword',                                        columns='target',                                        aggfunc=lambda x: len(x.unique()),                                        fill_value=0
)
# Look at output
keyword_by_target_dist

Мы уже видим несколько интересных дистрибутивов из нашей быстрой проверки. Слова wreck, wreckage и wrecked звучат похоже, но имеют явно одностороннее распределение классов, а wreckage имеет распределение, совершенно отличное от двух других слов.

В стороне: лемматизация и стемминг

Двумя распространенными методами НЛП являются лемматизация и выделение корней, которые могут помочь нормализовать термины, имеющие одинаковые семантические значения, но имеющие разные грамматические формы. Мы поговорим об этом более подробно позже, но вот несколько примеров:

- Лемматизация: is и are преобразуются в стандартный is

- Stemming: слова во множественном числе, такие как ships, преобразуются в форму единственного числа ship.

Эти методы могут помочь уменьшить ненужную размерность в наших данных наряду с уменьшением шума.

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

# Calculate the percentage of keyword instances in target class 1 
keyword_by_target_dist['keyword_bias'] = keyword_by_target_dist.apply(lambda row: row['id'][1]/(row['id'][0] + row['id'][1]), axis=1)
# Sort from high to low bias
keyword_by_target_dist = keyword_by_target_dist.sort_values(by='keyword_bias', ascending=False)
# Look at our output
keyword_by_target_dist

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

# Bar chart
fig = px.bar(keyword_by_target_dist.reset_index()[['keyword', 'keyword_bias']], 
                   x = "keyword",
                   y = 'keyword_bias'
                  )
fig.update_layout(xaxis_type='category',
                  title_text='Keyword Bias Towards Target')
fig.show()

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

Следующие шаги

Во Части 2 мы рассмотрим Location вместе с некоторыми другими мета-функциями нашего набора данных.

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

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