{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "## Специализация \"Машинное обучение и анализ данных\"\n", "
Автор материала: программист-исследователь Mail.Ru Group, старший преподаватель Факультета Компьютерных Наук ВШЭ [Юрий Кашницкий](https://yorko.github.io/)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#
Capstone проект №1
Идентификация пользователей по посещенным веб-страницам\n", "\n", "\n", "\n", "В этом проекте мы будем решать задачу идентификации пользователя по его поведению в сети Интернет. Это сложная и интересная задача на стыке анализа данных и поведенческой психологии. В качестве примера, компания Яндекс решает задачу идентификации взломщика почтового ящика по его поведению. В двух словах, взломщик будет себя вести не так, как владелец ящика: он может не удалять сообщения сразу по прочтении, как это делал хозяин, он будет по-другому ставить флажки сообщениям и даже по-своему двигать мышкой. Тогда такого злоумышленника можно идентифицировать и \"выкинуть\" из почтового ящика, предложив хозяину войти по SMS-коду. Этот пилотный проект описан в [статье](https://habrahabr.ru/company/yandex/blog/230583/) на Хабрахабре. Похожие вещи делаются, например, в Google Analytics и описываются в научных статьях, найти можно многое по фразам \"Traversal Pattern Mining\" и \"Sequential Pattern Mining\".\n", "\n", "\n", "Мы будем решать похожую задачу: по последовательности из нескольких веб-сайтов, посещенных подряд один и тем же человеком, мы будем идентифицировать этого человека. Идея такая: пользователи Интернета по-разному переходят по ссылкам, и это может помогать их идентифицировать (кто-то сначала в почту, потом про футбол почитать, затем новости, контакт, потом наконец – работать, кто-то – сразу работать).\n", "\n", "Будем использовать данные из [статьи](http://ceur-ws.org/Vol-1703/paper12.pdf) \"A Tool for Classification of Sequential Data\". И хотя мы не можем рекомендовать эту статью (описанные методы делеки от state-of-the-art, лучше обращаться к [книге](http://www.charuaggarwal.net/freqbook.pdf) \"Frequent Pattern Mining\" и последним статьям с ICDM), но данные там собраны аккуратно и представляют интерес.\n", "\n", "Имеются данные с прокси-серверов Университета Блеза Паскаля, они имеют очень простой вид. Для каждого пользователя заведен csv-файл с названием user\\*\\*\\*\\*.csv (где вместо звездочек – 4 цифры, соответствующие ID пользователя), а в нем посещения сайтов записаны в следующем формате:
\n", "\n", "
*timestamp, посещенный веб-сайт*
\n", "\n", "Скачать исходные данные можно по ссылке в статье, там же описание.\n", "Для этого задания хватит данных не по всем 3000 пользователям, а по 10 и 150. [Ссылка](https://drive.google.com/file/d/1AU3M_mFPofbfhFQa_Bktozq_vFREkWJA/view?usp=sharing) на архив *capstone_user_identification* (~7 Mb, в развернутом виде ~ 60 Mb). \n", "\n", "В финальном проекте уже придется столкнуться с тем, что не все операции можно выполнить за разумное время (скажем, перебрать с кросс-валидацией 100 комбинаций параметров случайного леса на этих данных Вы вряд ли сможете), поэтому мы будем использовать параллельно 2 выборки: по 10 пользователям и по 150. Для 10 пользователей будем писать и отлаживать код, для 150 – будет рабочая версия. \n", "\n", "Данные устроены следующем образом:\n", "\n", " - В каталоге 10users лежат 10 csv-файлов с названием вида \"user[USER_ID].csv\", где [USER_ID] – ID пользователя;\n", " - Аналогично для каталога 150users – там 150 файлов;\n", " - В каталоге 3users – игрушечный пример из 3 файлов, это для отладки кода предобработки, который Вы далее напишете.\n", "\n", "На 5 неделе будет задание по [соревнованию](https://www.kaggle.com/c/catch-me-if-you-can-intruder-detection-through-webpage-session-tracking2) Kaggle Inclass, которое организовано специально под Capstone проект нашей специализации. Соревнование уже открыто и, конечно, желающие могут начать уже сейчас.\n", "\n", "#
Неделя 1. Подготовка данных к анализу и построению моделей\n", "\n", "Первая часть проекта посвящена подготовке данных для дальнейшего описательного анализа и построения прогнозных моделей. Надо будет написать код для предобработки данных (исходно посещенные веб-сайты указаны для каждого пользователя в отдельном файле) и формирования единой обучающей выборки. Также в этой части мы познакомимся с разреженным форматом данных (матрицы `Scipy.sparse`), который хорошо подходит для данной задачи. \n", "\n", "**План 1 недели:**\n", " - Часть 1. Подготовка обучающей выборки\n", " - Часть 2. Работа с разреженным форматом данных" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Задание\n", "1. Заполните код в этой тетрадке \n", "2. Если вы проходите специализацию Яндеса и МФТИ, пошлите файл с ответами в соответствующем Programming Assignment.
Если вы проходите курс ODS, выберите ответы в [веб-форме](https://docs.google.com/forms/d/e/1FAIpQLSedmwHb4cOI32zKJmEP7RvgEjNoz5GbeYRc83qFXVH82KFgGA/viewform). \n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**В этой части проекта Вам могут быть полезны видеозаписи следующих лекций 1 и 2 недели курса \"Математика и Python для анализа данных\":**\n", " - [Циклы, функции, генераторы, list comprehension](https://www.coursera.org/learn/mathematics-and-python/lecture/Kd7dL/tsikly-funktsii-ghienieratory-list-comprehension)\n", " - [Чтение данных из файлов](https://www.coursera.org/learn/mathematics-and-python/lecture/8Xvwp/chtieniie-dannykh-iz-failov)\n", " - [Запись файлов, изменение файлов](https://www.coursera.org/learn/mathematics-and-python/lecture/vde7k/zapis-failov-izmienieniie-failov)\n", " - [Pandas.DataFrame](https://www.coursera.org/learn/mathematics-and-python/lecture/rcjAW/pandas-data-frame)\n", " - [Pandas. Индексация и селекция](https://www.coursera.org/learn/mathematics-and-python/lecture/lsXAR/pandas-indieksatsiia-i-sieliektsiia)\n", " \n", "**Кроме того, в задании будут использоваться библиотеки Python [`glob`](https://docs.python.org/3/library/glob.html), [`pickle`](https://docs.python.org/2/library/pickle.html) и класс [`csr_matrix`](https://docs.scipy.org/doc/scipy-0.18.1/reference/generated/scipy.sparse.csr_matrix.html) из `Scipy.sparse`.**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Наконец, для лучшей воспроизводимости результатов приведем список версий основных используемых в проекте библиотек: NumPy, SciPy, Pandas, Matplotlib, Statsmodels и Scikit-learn. Для этого воспользуемся расширением [watermark](https://github.com/rasbt/watermark). Рекомендуется использовать докер-контейнер открытого курса OpenDataScience по машинному обучению, инструкции [тут](https://goo.gl/RrwpNd)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# pip install watermark\n", "%load_ext watermark" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%watermark -v -m -p numpy,scipy,pandas,matplotlib,statsmodels,sklearn -g" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from __future__ import division, print_function\n", "# отключим всякие предупреждения Anaconda\n", "import warnings\n", "warnings.filterwarnings('ignore')\n", "from glob import glob\n", "import os\n", "import pickle\n", "#pip install tqdm\n", "from tqdm import tqdm_notebook\n", "import numpy as np\n", "import pandas as pd\n", "from scipy.sparse import csr_matrix" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Посмотрим на один из файлов с данными о посещенных пользователем (номер 31) веб-страницах.**" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Поменяйте на свой путь к данным\n", "PATH_TO_DATA = 'capstone_user_identification'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "user31_data = pd.read_csv(os.path.join(PATH_TO_DATA, \n", " '10users/user0031.csv'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "user31_data.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Поставим задачу классификации: идентифицировать пользователя по сессии из 10 подряд посещенных сайтов. Объектом в этой задаче будет сессия из 10 сайтов, последовательно посещенных одним и тем же пользователем, признаками – индексы этих 10 сайтов (чуть позже здесь появится \"мешок\" сайтов, подход Bag of Words). Целевым классом будет id пользователя.**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "###
Пример для иллюстрации
\n", "**Пусть пользователя всего 2, длина сессии – 2 сайта.**\n", "\n", "
user0001.csv
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
timestampsite
00:00:01vk.com
00:00:11google.com
00:00:16vk.com
00:00:20yandex.ru
\n", "\n", "
user0002.csv
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
timestampsite
00:00:02yandex.ru
00:00:14google.com
00:00:17facebook.com
00:00:25yandex.ru
\n", "\n", "Идем по 1 файлу, нумеруем сайты подряд: vk.com – 1, google.com – 2 и т.д. Далее по второму файлу. \n", "\n", "Отображение сайтов в их индесы должно получиться таким:\n", "\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
sitesite_id
vk.com1
google.com2
yandex.ru3
facebook.com4
\n", "\n", "Тогда обучающая выборка будет такой (целевой признак – user_id):\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
session_idsite1site2user_id
1121
2131
3322
4432
\n", "\n", "Здесь 1 объект – это сессия из 2 посещенных сайтов 1-ым пользователем (target=1). Это сайты vk.com и google.com (номер 1 и 2). И так далее, всего 4 сессии. Пока сессии у нас не пересекаются по сайтам, то есть посещение каждого отдельного сайта относится только к одной сессии." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Часть 1. Подготовка обучающей выборки\n", "Реализуйте функцию *prepare_train_set*, которая принимает на вход путь к каталогу с csv-файлами *path_to_csv_files* и параметр *session_length* – длину сессии, а возвращает 2 объекта:\n", "- DataFrame, в котором строки соответствуют уникальным сессиям из *session_length* сайтов, *session_length* столбцов – индексам этих *session_length* сайтов и последний столбец – ID пользователя\n", "- частотный словарь сайтов вида {'site_string': [site_id, site_freq]}, например для недавнего игрушечного примера это будет {'vk.com': (1, 2), 'google.com': (2, 2), 'yandex.ru': (3, 3), 'facebook.com': (4, 1)}\n", "\n", "Детали:\n", "- Смотрите чуть ниже пример вывода, что должна возвращать функция\n", "- Используйте `glob` (или аналоги) для обхода файлов в каталоге. Для определенности, отсортируйте список файлов лексикографически. Удобно использовать `tqdm_notebook` (или просто `tqdm` в случае python-скрипта) для отслеживания числа выполненных итераций цикла\n", "- Создайте частотный словарь уникальных сайтов (вида {'site_string': (site_id, site_freq)}) и заполняйте его по ходу чтения файлов. Начните с 1\n", "- Рекомендуется меньшие индексы давать более часто попадающимся сайтам (приницип наименьшего описания)\n", "- Не делайте entity recognition, считайте *google.com*, *http://www.google.com* и *www.google.com* разными сайтами (подключить entity recognition можно уже в рамках индивидуальной работы над проектом)\n", "- Скорее всего в файле число записей не кратно числу *session_length*. Тогда последняя сессия будет короче. Остаток заполняйте нулями. То есть если в файле 24 записи и сессии длины 10, то 3 сессия будет состоять из 4 сайтов, и ей мы сопоставим вектор [*site1_id*, *site2_id*, *site3_id*, *site4_id*, 0, 0, 0, 0, 0, 0, *user_id*] \n", "- В итоге некоторые сессии могут повторяться – оставьте как есть, не удаляйте дубликаты. Если в двух сессиях все сайты одинаковы, но сессии принадлежат разным пользователям, то тоже оставляйте как есть, это естественная неопределенность в данных\n", "- Не оставляйте в частотном словаре сайт 0 (уже в конце, когда функция возвращает этот словарь)\n", "- 150 файлов из *capstone_websites_data/150users/* у меня обработались за 1.7 секунды, но многое, конечно, зависит от реализации функции и от используемого железа. И вообще, первая реализация скорее всего будет не самой эффективной, дальше можно заняться профилированием (особенно если планируете запускать этот код для 3000 пользователей). Также эффективная реализация этой функции поможет нам на следующей неделе." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def prepare_train_set(path_to_csv_files, session_length=10):\n", " ''' ВАШ КОД ЗДЕСЬ '''" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Примените полученную функцию к игрушечному примеру, убедитесь, что все работает как надо.**" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!cat $PATH_TO_DATA/3users/user0001.csv" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!cat $PATH_TO_DATA/3users/user0002.csv" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!cat $PATH_TO_DATA/3users/user0003.csv" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "train_data_toy, site_freq_3users = prepare_train_set(os.path.join(PATH_TO_DATA, '3users'), \n", " session_length=10)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "train_data_toy" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Частоты сайтов (второй элемент кортежа) точно должны быть такими, нумерация может быть любой (первые элементы кортежей могут отличаться)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "site_freq_3users" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Примените полученную функцию к данным по 10 пользователям.\n", "\n", "** Вопрос 1. Сколько уникальных сессий из 10 сайтов в выборке с 10 пользователями?**" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "train_data_10users, site_freq_10users = ''' ВАШ КОД ЗДЕСЬ '''" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "** Вопрос 2. Сколько всего уникальных сайтов в выборке из 10 пользователей? **" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "''' ВАШ КОД ЗДЕСЬ '''" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Примените полученную функцию к данным по 150 пользователям.\n", "\n", "** Вопрос 3. Сколько уникальных сессий из 10 сайтов в выборке с 150 пользователями?**" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%time\n", "train_data_150users, site_freq_150users = ''' ВАШ КОД ЗДЕСЬ '''" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "** Вопрос 4. Сколько всего уникальных сайтов в выборке из 150 пользователей? **" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "''' ВАШ КОД ЗДЕСЬ '''" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "** Вопрос 5. Какой из этих сайтов НЕ входит в топ-10 самых популярных сайтов среди посещенных 150 пользователями?**\n", "- www.google.fr\n", "- www.youtube.com\n", "- safebrowsing-cache.google.com\n", "- www.linkedin.com" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "''' ВАШ КОД ЗДЕСЬ '''" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Для дальнейшего анализа запишем полученные объекты DataFrame в csv-файлы.**" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "train_data_10users.to_csv(os.path.join(PATH_TO_DATA, \n", " 'train_data_10users.csv'), \n", " index_label='session_id', float_format='%d')\n", "train_data_150users.to_csv(os.path.join(PATH_TO_DATA, \n", " 'train_data_150users.csv'), \n", " index_label='session_id', float_format='%d')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Часть 2. Работа с разреженным форматом данных" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Если так подумать, то полученные признаки *site1*, ..., *site10* смысла не имеют как признаки в задаче классификации. А вот если воспользоваться идеей мешка слов из анализа текстов – это другое дело. Создадим новые матрицы, в которых строкам будут соответствовать сессии из 10 сайтов, а столбцам – индексы сайтов. На пересечении строки $i$ и столбца $j$ будет стоять число $n_{ij}$ – cколько раз сайт $j$ встретился в сессии номер $i$. Делать это будем с помощью разреженных матриц Scipy – [csr_matrix](https://docs.scipy.org/doc/scipy-0.18.1/reference/generated/scipy.sparse.csr_matrix.html). Прочитайте документацию, разберитесь, как использовать разреженные матрицы и создайте такие матрицы для наших данных. Сначала проверьте на игрушечном примере, затем примените для 10 и 150 пользователей. \n", "\n", "Обратите внимание, что в коротких сессиях, меньше 10 сайтов, у нас остались нули, так что первый признак (сколько раз попался 0) по смыслу отличен от остальных (сколько раз попался сайт с индексом $i$). Поэтому первый столбец разреженной матрицы надо будет удалить. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X_toy, y_toy = train_data_toy.iloc[:, :-1].values, train_data_toy.iloc[:, -1].values" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X_toy" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X_sparse_toy = csr_matrix ''' ВАШ КОД ЗДЕСЬ ''' " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Размерность разреженной матрицы должна получиться равной 11, поскольку в игрушечном примере 3 пользователя посетили 11 уникальных сайтов.**" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X_sparse_toy.todense()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X_10users, y_10users = train_data_10users.iloc[:, :-1].values, \\\n", " train_data_10users.iloc[:, -1].values\n", "X_150users, y_150users = train_data_150users.iloc[:, :-1].values, \\\n", " train_data_150users.iloc[:, -1].values" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X_sparse_10users = ''' ВАШ КОД ЗДЕСЬ '''\n", "X_sparse_150users = ''' ВАШ КОД ЗДЕСЬ '''" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Сохраним эти разреженные матрицы с помощью [pickle](https://docs.python.org/2/library/pickle.html) (сериализация в Python), также сохраним вектора *y_10users, y_150users* – целевые значения (id пользователя) в выборках из 10 и 150 пользователей. То что названия этих матриц начинаются с X и y, намекает на то, что на этих данных мы будем проверять первые модели классификации.\n", "Наконец, сохраним также и частотные словари сайтов для 3, 10 и 150 пользователей.**" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "with open(os.path.join(PATH_TO_DATA, 'X_sparse_10users.pkl'), 'wb') as X10_pkl:\n", " pickle.dump(X_sparse_10users, X10_pkl, protocol=2)\n", "with open(os.path.join(PATH_TO_DATA, 'y_10users.pkl'), 'wb') as y10_pkl:\n", " pickle.dump(y_10users, y10_pkl, protocol=2)\n", "with open(os.path.join(PATH_TO_DATA, 'X_sparse_150users.pkl'), 'wb') as X150_pkl:\n", " pickle.dump(X_sparse_150users, X150_pkl, protocol=2)\n", "with open(os.path.join(PATH_TO_DATA, 'y_150users.pkl'), 'wb') as y150_pkl:\n", " pickle.dump(y_150users, y150_pkl, protocol=2)\n", "with open(os.path.join(PATH_TO_DATA, 'site_freq_3users.pkl'), 'wb') as site_freq_3users_pkl:\n", " pickle.dump(site_freq_3users, site_freq_3users_pkl, protocol=2)\n", "with open(os.path.join(PATH_TO_DATA, 'site_freq_10users.pkl'), 'wb') as site_freq_10users_pkl:\n", " pickle.dump(site_freq_10users, site_freq_10users_pkl, protocol=2)\n", "with open(os.path.join(PATH_TO_DATA, 'site_freq_150users.pkl'), 'wb') as site_freq_150users_pkl:\n", " pickle.dump(site_freq_150users, site_freq_150users_pkl, protocol=2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Чисто для подстраховки проверим, что число столбцов в разреженных матрицах `X_sparse_10users` и `X_sparse_150users` равно ранее посчитанным числам уникальных сайтов для 10 и 150 пользователей соответственно.**" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "assert X_sparse_10users.shape[1] == len(site_freq_10users)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "assert X_sparse_150users.shape[1] == len(site_freq_150users)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Пути улучшения\n", "- можно обработать исходные данные по 3000 пользователей; обучать на такой выборке модели лучше при наличии доступа к хорошим мощностям (можно арендовать инстанс Amazon EC2, как именно, описано [тут](https://habrahabr.ru/post/280562/)). Хотя далее в курсе мы познакомимся с алгоритмами, способными обучаться на больших выборках при малых вычислительных потребностях;\n", "- помимо явного создания разреженного формата можно еще составить выборки с помощью `CountVectorizer`, `TfidfVectorizer` и т.п. Поскольку данные по сути могут быть описаны как последовательности, то можно вычислять n-граммы сайтов. Работает все это или нет, мы будем проверять в [соревновании](https://inclass.kaggle.com/c/catch-me-if-you-can-intruder-detection-through-webpage-session-tracking2) Kaggle Inclass (желающие могут начать уже сейчас).\n", "\n", "На следующей неделе мы еще немного поготовим данные и потестируем первые гипотезы, связанные с нашими наблюдениями. " ] } ], "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.6.4" } }, "nbformat": 4, "nbformat_minor": 1 }