[mlcourse.ai](https://mlcourse.ai) – открытый курс OpenDataScience по машинному обучению \n",
"\n",
" \n",
"Автор материала: Ольга Дайховская (@aiho в Slack ODS). Материал распространяется на условиях лицензии [Creative Commons CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/). Можно использовать в любых целях (редактировать, поправлять и брать за основу), кроме коммерческих, но с обязательным упоминанием автора материала"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#
Домашнее задание № 4 (демо).\n",
"##
Прогнозирование популярности статей на TechMedia (Хабр) с помощью линейных моделей\n",
" \n",
"**В задании Вам предлагается разобраться с тем, как работает TfidfVectorizer и DictVectorizer, затем обучить и настроить модель линейной регрессии Ridge на данных о публикациях на Хабрахабре. Пройдя все шаги, вы сможете получить бейзлайн для [соревнования](https://www.kaggle.com/c/howpop-habrahabr-favs-lognorm) (несмотря на old в названии, для этого задания соревнование актуально). \n",
"Ответьте на все вопросы в этой тетрадке и заполните ответы в [гугл-форме](https://docs.google.com/forms/d/1gPt401drm84N2kdezwGWtPJN_JpaFqXoh6IwlWOslb4).**"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Описание соревнования**\n",
"\n",
"Предскажите, как много звездочек наберет статья, зная только ее текст и время публикации\n",
"\n",
"Необходимо предсказать популярность поста на Хабре по содержанию и времени публикации. Как известно, пользователи Хабра могут добавлять статьи к себе в избранное. Общее количество пользователей, которое это сделали отображается у статьи количеством звездочек. Будем считать, что число звездочек, поставленных статье, наиболее хорошо отражает ее популярность.\n",
"\n",
"Более формально, в качестве метрики популярности статьи будем использовать долю статей за последний месяц, у которых количество звездочек меньше чем у текущей статьи. А точнее, доле числа звездочек можно поставить в соответствие квантили стандартного распределения, таким образом получаем числовую характеристику популярности статьи. Популярность статьи 0 означает, что статья получила ровно столько звездочек, сколько в среднем получают статьи. И соответственно чем больше звездочек получила статья по сравнению со средним, тем выше это число."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Приступим:** импортируем необходимые библиотеки и скачаем данные"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import pandas as pd\n",
"import scipy\n",
"from sklearn.feature_extraction import DictVectorizer\n",
"from sklearn.feature_extraction.text import TfidfVectorizer\n",
"from sklearn.linear_model import Ridge\n",
"from sklearn.metrics import mean_squared_error\n",
"from sklearn.model_selection import train_test_split\n",
"\n",
"%matplotlib inline\n",
"from matplotlib import pyplot as plt"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Скачайте [данные](https://drive.google.com/file/d/1nV2qV9otN3LnVSDqy95hvpJdb6aWtATk/view?usp=sharing) соревнования (данные были удалены с Kaggle ради организации последующего идентичного соревнования, так что тут ссылка на Google Drive)."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# при необходимости поменяйте путь к данным \n",
"train_df = pd.read_csv('../../data/howpop_train.csv')\n",
"test_df = pd.read_csv('../../data/howpop_test.csv')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"train_df.head(1).T"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"train_df.shape, test_df.shape"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Убедимся, что данные отсортированы по признаку `published`"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"train_df['published'].apply(lambda ts: pd.to_datetime(ts).value).plot();"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Чтобы ответить на вопросы 1 и 2, можно использовать [pandas.DataFrame.corr()](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.corr.html), [pandas.to_datetime()](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.to_datetime.html) и [pandas.Series.value_counts()](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.Series.value_counts.html)**"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Вопрос 1. Есть ли в train_df признаки, корреляция между которыми больше 0.9? Обратите внимание, именно различные признаки - корреляция признака с самим собой естественно больше 0.9 :)\n",
"- да\n",
"- нет\n",
"- не знаю"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ваш код здесь"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Вопрос 2. В каком году было больше всего публикаций? (Рассматриваем train_df)\n",
"- 2014\n",
"- 2015\n",
"- 2016\n",
"- 2017"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ваш код здесь"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Разбиение на train/valid\n",
"Используем только признаки 'author', 'flow', 'domain' и 'title'"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"features = ['author', 'flow', 'domain','title']\n",
"train_size = int(0.7 * train_df.shape[0])"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"len(train_df), train_size"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"X, y = train_df.loc[:, features], train_df['favs_lognorm'] #отделяем признаки от целевой переменной\n",
"\n",
"X_test = test_df.loc[:, features]"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"X_train, X_valid = X.iloc[:train_size, :], X.iloc[train_size:,:]\n",
"\n",
"y_train, y_valid = y.iloc[:train_size], y.iloc[train_size:]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Применение TfidfVectorizer\n",
"\n",
"**TF-IDF** (от англ. TF — term frequency, IDF — inverse document frequency) — статистическая мера, используемая для оценки важности слова в контексте документа, являющегося частью коллекции документов или корпуса. Вес некоторого слова пропорционален количеству употребления этого слова в документе, и обратно пропорционален частоте употребления слова в других документах коллекции. [Подробнее в источнике](https://ru.wikipedia.org/wiki/TF-IDF)\n",
"\n",
"TfidfVectorizer преобразует тексты в матрицу TF-IDF признаков.\n",
"\n",
"**Основные параметры TfidfVectorizer в sklearn:**\n",
"- **min_df** - при построении словаря слова, которые встречаются *реже*, чем указанное значение, игнорируются\n",
"- **max_df** - при построении словаря слова, которые встречаются *чаще*, чем указанное значение, игнорируются\n",
"- **analyzer** - определяет, строятся ли признаки по словам или по символам (буквам)\n",
"- **ngram_range** - определяет, формируются ли признаки только из отдельных слов или из нескольких слов (в случае с analyzer='char' задает количество символов). Например, если указать analyzer='word' и ngram_range=(1,3),то признаки будут формироваться из отдельных слов, из пар слов и из троек слов.\n",
"- **stop_words** - слова, которые игнорируются при построении матрицы\n",
"\n",
"Более подробно с параметрами можно ознакомиться в [документации](http://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.TfidfVectorizer.html)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Инициализируйте TfidfVectorizer с параметрами min_df=3, max_df=0.3 и ngram_range=(1, 3). \n",
"Примените метод fit_transform к X_train['title'] и метод transform к X_valid['title'] и X_test['title']**"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Вопрос 3. Какой размер у полученного словаря?\n",
"- 43789\n",
"- 50624\n",
"- 93895\n",
"- 74378"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"vectorizer_title = # ваш код здесь\n",
"\n",
"X_train_title = # и здесь\n",
"X_valid_title = # и тут тоже\n",
"X_test_title = # и тут"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Можно посмотреть словарь в виде {'термин': индекс признака,...}\n",
"vectorizer_title.vocabulary_"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ваш код здесь"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Вопрос 4. Какой индекс у слова 'python'?\n",
"- 1\n",
"- 10\n",
"- 9065\n",
"- 15679"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ваш код здесь"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Инициализируйте TfidfVectorizer, указав analyzer='char'. \n",
"Примените метод fit_transform к X_train['title'] и метод transform к X_valid['title'] и X_test['title']**"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Вопрос 5. Какой размер у полученного словаря?\n",
"- 218\n",
"- 510\n",
"- 125\n",
"- 981"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"vectorizer_title_ch = # ваш код здесь\n",
"\n",
"X_train_title_ch = #...\n",
"X_valid_title_ch = #...\n",
"X_test_title_ch = #..."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Здесь так же можно посмотреть словарь\n",
"# Заметьте насколько отличаются словари для TfidfVectorizer с analyzer='word' и analyzer='char'\n",
"vectorizer_title_ch.vocabulary_"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ваш код здесь"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Работа с категориальными признаками\n",
"\n",
"Для обработки категориальных признаков 'author', 'flow', 'domain' мы будем использовать DictVectorizer из sklearn."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"feats = ['author', 'flow', 'domain']\n",
"X_train[feats][:5]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Рассмотрим как он работает на примере первых пяти строк"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# сначала заполняем пропуски прочерком\n",
"X_train[feats][:5].fillna('-')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Преобразуем датафрейм в словарь, где ключами являются индексы объектов (именно для этого мы транспонировали датафрейм),\n",
"# а значениями являются словари в виде 'название_колонки':'значение'\n",
"X_train[feats][:5].fillna('-').T.to_dict()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# В DictVectorizer нам нужно будет передать список словарей для каждого объекта в виде 'название_колонки':'значение',\n",
"# поэтому используем .values()\n",
"X_train[feats][:5].fillna('-').T.to_dict().values()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# В итоге получается разреженная матрица\n",
"dict_vect = DictVectorizer()\n",
"dict_vect_matrix = dict_vect.fit_transform(X_train[feats][:5].fillna('-').T.to_dict().values())\n",
"dict_vect_matrix"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Но можно преобразовать ее в numpy array с помощью .toarray()\n",
"dict_vect_matrix.toarray()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# В получившейся матрице 5 строк (по числу объектов) и 9 столбцов\n",
"# Далее разберемся почему колонок именно 9\n",
"dict_vect_matrix.shape"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Посмотрим сколько уникальных значений в каждой колонке. \n",
"Суммарно их 9 - столько же, сколько и колонок. Это объясняется тем, что для категориальных признаков со строковыми значениями DictVectorizer делает кодирование бинарными признаками - каждому уникальному значению признака соответствует один новый бинарный признак, который равен 1 только в том случае, если в исходной матрице этот признак принимает значение, которому соответствует эта колонка новой матрицы."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"for col in feats:\n",
" print(col,len(X_train[col][:5].fillna('-').unique()))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Также можно посмотреть что означает каждая колонка полученной матрицы"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# например, самая первая колонка называется 'author=@DezmASter' - то есть принимает значение 1 только если автор @DezmASter\n",
"dict_vect.feature_names_"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Инициализируйте DictVectorizer с параметрами по умолчанию. \n",
"Примените метод fit_transform к X_train[feats] и метод transform к X_valid[feats] и X_test[feats]**"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"vectorizer_feats = #ваш код здесь\n",
"\n",
"X_train_feats = #...\n",
"X_valid_feats = #...\n",
"X_test_feats = #..."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"X_train_feats.shape"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Соединим все полученные матрицы при помощи scipy.sparse.hstack()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"X_train_new = scipy.sparse.hstack([X_train_title, X_train_feats, X_train_title_ch])\n",
"X_valid_new = scipy.sparse.hstack([X_valid_title, X_valid_feats, X_valid_title_ch])\n",
"X_test_new = scipy.sparse.hstack([X_test_title, X_test_feats, X_test_title_ch])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Обучение модели\n",
"\n",
"Далее будем использовать Ridge, линейную модель с l2-регуляризацией.\n",
"[Документация](http://scikit-learn.org/stable/modules/generated/sklearn.linear_model.Ridge.html)\n",
"\n",
"Основной параметр Ridge - **alpha, коэффициент регуляризации**. Регуляризация используется для улучшения обобщающей способности модели - прибавляя к функционалу потерь сумму квадратов весов, умноженную на коэффициент регуляризации (та самая alpha), мы штрафуем модель за слишком большие значения весов и не позволяем ей переобучаться. Чем больше этот коээфициент, тем сильнее эффект."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Обучите две модели на X_train_new, y_train, задав в первой alpha=0.1 и random_state = 1, а во второй alpha=1.0 и random_state = 1**\n",
"\n",
"**Рассчитайте среднеквадратичную ошибку каждой модели (mean_squared_error). Сравните значения ошибки на обучающей и тестовой выборках и ответьте на вопросы.**"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Вопрос 6. Выберите верные утверждения:\n",
"- обе модели показывают одинаковый результат (среднеквадратичная ошибка отличается не больше чем на тысячные), регуляризация ничего не меняет\n",
"- при alpha=0.1 модель переобучается\n",
"- среднеквадратичная ошибка первой модели на тесте меньше\n",
"- при alpha=1.0 у модели обобщающая способность лучше, чем у при alpha=0.1"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%%time\n",
"model1 = #ваш код здесь\n",
"#здесь тоже ваш код"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"train_preds1 = model1.predict(X_train_new)\n",
"valid_preds1 = model1.predict(X_valid_new)\n",
"\n",
"print('Ошибка на трейне',mean_squared_error(y_train, train_preds1))\n",
"print('Ошибка на тесте',mean_squared_error(y_valid, valid_preds1))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%%time\n",
"model2 = #ваш код здесь\n",
"#здесь тоже ваш код"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"train_preds2 = model2.predict(X_train_new)\n",
"valid_preds2 = model2.predict(X_valid_new)\n",
"\n",
"print('Ошибка на трейне',mean_squared_error(y_train, train_preds2))\n",
"print('Ошибка на тесте',mean_squared_error(y_valid, valid_preds2))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Baseline\n",
"\n",
"**Теперь попытаемся получить бейзлайн для соревования - используйте Ridge с параметрами по умолчанию и обучите модель на всех данных - соедините X_train_new X_valid_new (используйте scipy.sparse.vstack()), а целевой переменной будет y.**"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%%time\n",
"model = # ваш код здесь\n",
"\n",
"# обучите модель на всех данных\n",
"\n",
"test_preds = model.predict(X_test_new)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"sample_submission = pd.read_csv('../../data/habr_sample_submission.csv', \n",
" index_col='url')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"sample_submission.head()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"ridge_submission = sample_submission.copy()\n",
"ridge_submission['favs_lognorm'] = test_preds\n",
"# это будет бейзлайн \"Простое решение\"\n",
"ridge_submission.to_csv('ridge_baseline.csv') "
]
}
],
"metadata": {
"anaconda-cloud": {},
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.6"
}
},
"nbformat": 4,
"nbformat_minor": 1
}