Автор материала: Ольга Дайховская (@aiho в Slack ODS). Материал распространяется на условиях лицензии Creative Commons CC BY-NC-SA 4.0. Можно использовать в любых целях (редактировать, поправлять и брать за основу), кроме коммерческих, но с обязательным упоминанием автора материала
В задании Вам предлагается разобраться с тем, как работает TfidfVectorizer и DictVectorizer, затем обучить и настроить модель линейной регрессии Ridge на данных о публикациях на Хабрахабре. Пройдя все шаги, вы сможете получить бейзлайн для соревнования (несмотря на old в названии, для этого задания соревнование актуально). Ответьте на все вопросы в этой тетрадке и заполните ответы в гугл-форме.
Описание соревнования
Предскажите, как много звездочек наберет статья, зная только ее текст и время публикации
Необходимо предсказать популярность поста на Хабре по содержанию и времени публикации. Как известно, пользователи Хабра могут добавлять статьи к себе в избранное. Общее количество пользователей, которое это сделали отображается у статьи количеством звездочек. Будем считать, что число звездочек, поставленных статье, наиболее хорошо отражает ее популярность.
Более формально, в качестве метрики популярности статьи будем использовать долю статей за последний месяц, у которых количество звездочек меньше чем у текущей статьи. А точнее, доле числа звездочек можно поставить в соответствие квантили стандартного распределения, таким образом получаем числовую характеристику популярности статьи. Популярность статьи 0 означает, что статья получила ровно столько звездочек, сколько в среднем получают статьи. И соответственно чем больше звездочек получила статья по сравнению со средним, тем выше это число.
Приступим: импортируем необходимые библиотеки и скачаем данные
import numpy as np
import pandas as pd
import scipy
from sklearn.feature_extraction import DictVectorizer
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import Ridge
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split
%matplotlib inline
from matplotlib import pyplot as plt
Скачайте данные соревнования (данные были удалены с Kaggle ради организации последующего идентичного соревнования, так что тут ссылка на Google Drive).
# при необходимости поменяйте путь к данным
train_df = pd.read_csv('../../data/howpop_train.csv')
test_df = pd.read_csv('../../data/howpop_test.csv')
train_df.head(1).T
train_df.shape, test_df.shape
Убедимся, что данные отсортированы по признаку published
train_df['published'].apply(lambda ts: pd.to_datetime(ts).value).plot();
Чтобы ответить на вопросы 1 и 2, можно использовать pandas.DataFrame.corr(), pandas.to_datetime() и pandas.Series.value_counts()
Вопрос 1. Есть ли в train_df признаки, корреляция между которыми больше 0.9? Обратите внимание, именно различные признаки - корреляция признака с самим собой естественно больше 0.9 :)
# ваш код здесь
Вопрос 2. В каком году было больше всего публикаций? (Рассматриваем train_df)
# ваш код здесь
Используем только признаки 'author', 'flow', 'domain' и 'title'
features = ['author', 'flow', 'domain','title']
train_size = int(0.7 * train_df.shape[0])
len(train_df), train_size
X, y = train_df.loc[:, features], train_df['favs_lognorm'] #отделяем признаки от целевой переменной
X_test = test_df.loc[:, features]
X_train, X_valid = X.iloc[:train_size, :], X.iloc[train_size:,:]
y_train, y_valid = y.iloc[:train_size], y.iloc[train_size:]
TF-IDF (от англ. TF — term frequency, IDF — inverse document frequency) — статистическая мера, используемая для оценки важности слова в контексте документа, являющегося частью коллекции документов или корпуса. Вес некоторого слова пропорционален количеству употребления этого слова в документе, и обратно пропорционален частоте употребления слова в других документах коллекции. Подробнее в источнике
TfidfVectorizer преобразует тексты в матрицу TF-IDF признаков.
Основные параметры TfidfVectorizer в sklearn:
Более подробно с параметрами можно ознакомиться в документации
Инициализируйте TfidfVectorizer с параметрами min_df=3, max_df=0.3 и ngram_range=(1, 3).
Примените метод fit_transform к X_train['title'] и метод transform к X_valid['title'] и X_test['title']
Вопрос 3. Какой размер у полученного словаря?
vectorizer_title = # ваш код здесь
X_train_title = # и здесь
X_valid_title = # и тут тоже
X_test_title = # и тут
# Можно посмотреть словарь в виде {'термин': индекс признака,...}
vectorizer_title.vocabulary_
# ваш код здесь
Вопрос 4. Какой индекс у слова 'python'?
# ваш код здесь
Инициализируйте TfidfVectorizer, указав analyzer='char'.
Примените метод fit_transform к X_train['title'] и метод transform к X_valid['title'] и X_test['title']
Вопрос 5. Какой размер у полученного словаря?
vectorizer_title_ch = # ваш код здесь
X_train_title_ch = #...
X_valid_title_ch = #...
X_test_title_ch = #...
# Здесь так же можно посмотреть словарь
# Заметьте насколько отличаются словари для TfidfVectorizer с analyzer='word' и analyzer='char'
vectorizer_title_ch.vocabulary_
# ваш код здесь
Для обработки категориальных признаков 'author', 'flow', 'domain' мы будем использовать DictVectorizer из sklearn.
feats = ['author', 'flow', 'domain']
X_train[feats][:5]
Рассмотрим как он работает на примере первых пяти строк
# сначала заполняем пропуски прочерком
X_train[feats][:5].fillna('-')
# Преобразуем датафрейм в словарь, где ключами являются индексы объектов (именно для этого мы транспонировали датафрейм),
# а значениями являются словари в виде 'название_колонки':'значение'
X_train[feats][:5].fillna('-').T.to_dict()
# В DictVectorizer нам нужно будет передать список словарей для каждого объекта в виде 'название_колонки':'значение',
# поэтому используем .values()
X_train[feats][:5].fillna('-').T.to_dict().values()
# В итоге получается разреженная матрица
dict_vect = DictVectorizer()
dict_vect_matrix = dict_vect.fit_transform(X_train[feats][:5].fillna('-').T.to_dict().values())
dict_vect_matrix
# Но можно преобразовать ее в numpy array с помощью .toarray()
dict_vect_matrix.toarray()
# В получившейся матрице 5 строк (по числу объектов) и 9 столбцов
# Далее разберемся почему колонок именно 9
dict_vect_matrix.shape
Посмотрим сколько уникальных значений в каждой колонке.
Суммарно их 9 - столько же, сколько и колонок. Это объясняется тем, что для категориальных признаков со строковыми значениями DictVectorizer делает кодирование бинарными признаками - каждому уникальному значению признака соответствует один новый бинарный признак, который равен 1 только в том случае, если в исходной матрице этот признак принимает значение, которому соответствует эта колонка новой матрицы.
for col in feats:
print(col,len(X_train[col][:5].fillna('-').unique()))
Также можно посмотреть что означает каждая колонка полученной матрицы
# например, самая первая колонка называется 'author=@DezmASter' - то есть принимает значение 1 только если автор @DezmASter
dict_vect.feature_names_
Инициализируйте DictVectorizer с параметрами по умолчанию.
Примените метод fit_transform к X_train[feats] и метод transform к X_valid[feats] и X_test[feats]
vectorizer_feats = #ваш код здесь
X_train_feats = #...
X_valid_feats = #...
X_test_feats = #...
X_train_feats.shape
Соединим все полученные матрицы при помощи scipy.sparse.hstack()
X_train_new = scipy.sparse.hstack([X_train_title, X_train_feats, X_train_title_ch])
X_valid_new = scipy.sparse.hstack([X_valid_title, X_valid_feats, X_valid_title_ch])
X_test_new = scipy.sparse.hstack([X_test_title, X_test_feats, X_test_title_ch])
Далее будем использовать Ridge, линейную модель с l2-регуляризацией. Документация
Основной параметр Ridge - alpha, коэффициент регуляризации. Регуляризация используется для улучшения обобщающей способности модели - прибавляя к функционалу потерь сумму квадратов весов, умноженную на коэффициент регуляризации (та самая alpha), мы штрафуем модель за слишком большие значения весов и не позволяем ей переобучаться. Чем больше этот коээфициент, тем сильнее эффект.
Обучите две модели на X_train_new, y_train, задав в первой alpha=0.1 и random_state = 1, а во второй alpha=1.0 и random_state = 1
Рассчитайте среднеквадратичную ошибку каждой модели (mean_squared_error). Сравните значения ошибки на обучающей и тестовой выборках и ответьте на вопросы.
Вопрос 6. Выберите верные утверждения:
%%time
model1 = #ваш код здесь
#здесь тоже ваш код
train_preds1 = model1.predict(X_train_new)
valid_preds1 = model1.predict(X_valid_new)
print('Ошибка на трейне',mean_squared_error(y_train, train_preds1))
print('Ошибка на тесте',mean_squared_error(y_valid, valid_preds1))
%%time
model2 = #ваш код здесь
#здесь тоже ваш код
train_preds2 = model2.predict(X_train_new)
valid_preds2 = model2.predict(X_valid_new)
print('Ошибка на трейне',mean_squared_error(y_train, train_preds2))
print('Ошибка на тесте',mean_squared_error(y_valid, valid_preds2))
Теперь попытаемся получить бейзлайн для соревования - используйте Ridge с параметрами по умолчанию и обучите модель на всех данных - соедините X_train_new X_valid_new (используйте scipy.sparse.vstack()), а целевой переменной будет y.
%%time
model = # ваш код здесь
# обучите модель на всех данных
test_preds = model.predict(X_test_new)
sample_submission = pd.read_csv('../../data/habr_sample_submission.csv',
index_col='url')
sample_submission.head()
ridge_submission = sample_submission.copy()
ridge_submission['favs_lognorm'] = test_preds
# это будет бейзлайн "Простое решение"
ridge_submission.to_csv('ridge_baseline.csv')