{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "## Открытый курс по машинному обучению. Сессия № 2\n", "\n", "###
Автор материала: Елена Девятайкина (@elenadevyataykina)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# JusticeHunter или как понять, преступление ли это или нелепая случайность" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Я часто вижу статьи о том, что теперь есть возможность определить, добьется ли человек потенциальных успехов в профессиональной или творческой сфере, грозит ли ему болезнь Альцгеймера или Паркинсона лет так через 50, как найти вторую половину, чтобы сразу и на всю жизнь, а также вообще, что за человек и какой ориентации. \n", "\n", "Но что касается исследований психологических заболеваний, понимания мотивации человека к тому или иному действию, в частности к преступлениям, уже информации не так много (возможна она недоступна или же действительно здесь сделать выводы сложнее). Но так как в тюрьмы к заключенным отправляют огромное количество консультантов для бесед с ними, а также для выяснения более подробной информации о них, я склонна предполагать, что здесь слишком много надо информации изучить, прежде чем приступить к ее интерпретации. \n", "\n", "И вот, прочитав книгу **Mind Hunter: Inside FBI’s Elite Serial Crime Unit** и посмотрев сериал **MindHunter** (рекомендую для любителей криминалистики, ведь его снимал сам ФИНЧЕР), я решила сделать свое исследование на эту тему.\n", "\n", "Изначально я рассчитывала получить данные из университета Рэдфорда (это, пожалуй, один из самых крупных датасетов, где собрано множество информации о серийных убийцах за 30 лет проведения данного исследования), но не срослось (если у кого-то есть эти данные, то я буду безумно благодарна, если вы поделитесь ими со мной).\n", "\n", "Вместо этого был найден датасет на kaggle, где собрана информация о преступлениях, тех, кто их совершил и пострадавших (жертвах). Здесь рассматриваются данные из отделений полиции нескольких штатов Америки о преступлениях, совершенных с 1980 - 2014. Целью проекта является понять, можно ли отличить преднамеренное преступление от причинения вреда по неосторожности, а также понять, какие из предикторов являются наиболее значимыми при определении типа преступления и достаточно ли их вообще." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Отключим предупреждения Anaconda\n", "import warnings\n", "warnings.simplefilter('ignore')\n", "\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "import seaborn as sns\n", "sns.set(style = 'darkgrid') #зададим базовое оформление\n", "%pylab inline\n", "\n", "from sklearn.preprocessing import OneHotEncoder, LabelEncoder\n", "from sklearn.utils import shuffle" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Описание данных" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Возьмем из данных только те столбцы, которые будут важны для дальнейшего исследования\n", "(также в данных содержится информация о полиции, сроке расследования, а также о человеке, проводившем допрос)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df = pd.read_csv('dataframe.csv', usecols=['Year', 'Month', 'Victim Sex', 'Victim Age', 'Victim Race', 'Perpetrator Sex', 'Perpetrator Age', 'Perpetrator Race', 'Relationship', 'Weapon', 'Victim Count', 'Crime Type'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Посмотрим как выглядят данные" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Year - год совершения преступления\n", "- Month - месяц совершения преступления\n", "- Victim Sex - пол жертвы\n", "- Victim Age - возраст жертвы\n", "- Victim Race - раса жертвы\n", "- Perpetrator Sex - пол исполнителя\n", "- Perpetrator Age - возраст исполнителя\n", "- Perpetrator Race - раса исполнителя\n", "- Relationship - Родственные связи между исполнителем и жертвой\n", "- Weapon - оружие\n", "- Victim Count - количество жертв\n", "- Crime Type - целевой признак, убийство (преднамеренное нанесение вреда) или приченение вреда по неосторожности" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df.info()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Как видно из данных есть несколько категориальных признаков, поэтому воспользуемся Label Encoding" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "categorical_columns = df.columns[df.dtypes == 'object']\n", "numerical_columns = ['Year', 'Victim Age', 'Perpetrator Age', 'Victim Count']" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "label_encoder = LabelEncoder()\n", "for column in categorical_columns:\n", " df[column] = label_encoder.fit_transform(df[column])\n", "df.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Посмотрим на корреляцию между признаками" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "corr_features = df.drop('Crime Type', axis=1).corr()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sns.set(font_scale=1.5)\n", "#cmap = sns.diverging_palette(220, 10, as_cmap=True)\n", "f, ax = plt.subplots(figsize=(16, 9))\n", "sns.heatmap(corr_features, cmap='viridis', annot=True, ax=ax, fmt='.2f')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Видим, что достаточно высокая корреляция между расой жертвы и преступника" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Посмотрим на соотношение типа совершаемых убийств между женщинами и мужчинами, где\n", "- 0 - причинение вреда по неосторожности\n", "- 1 - убийство или преднамеренное нанесение ущерба" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plt.figure(figsize(16,6))\n", "plt.subplot(121)\n", "stat_red = df[df['Perpetrator Sex'] == 1].groupby('Crime Type')['Crime Type'].agg(lambda x: float(len(x))/df[df['Perpetrator Sex'] == 1].shape[0])\n", "stat_red.plot(kind='bar', color='g', width=0.9)\n", "plt.xticks(rotation=0)\n", "plt.ylabel('Type of crime', fontsize=14)\n", "plt.xlabel('Men', fontsize=14)\n", "\n", "plt.subplot(122)\n", "stat_white = df[df['Perpetrator Sex'] == 0].groupby('Crime Type')['Crime Type'].agg(lambda x: float(len(x))/df[df['Perpetrator Sex'] == 2].shape[0])\n", "stat_white.plot(color='y', kind='bar', width=0.9)\n", "plt.xticks(rotation=0)\n", "plt.ylabel('Type of crime', fontsize=14)\n", "plt.xlabel('Women', fontsize=14)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В данных есть те преступления, где виновный неизвестен и их возраст указан как 0. Я уберу из выборки такие данные" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df = df[df['Perpetrator Age'] != 0]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Дополнительные данные можно получить только с помощью дополнительных сведений о преступнике или жертве и характере преступления, поэтому дальнейшие действия будут производиться на тех данных, которые есть" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Перейдем к обучению модели" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Random Forest" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Сначала попробуем Random Forest, так как он не требует детальную предварительную настройку, а также есть возможность выявить степень предиктора, что нам и необходимо согласно поставленной цели" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from sklearn.model_selection import train_test_split\n", "from sklearn.metrics import classification_report\n", "from sklearn.ensemble import RandomForestClassifier" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Так как отдельно тестовой выборки нет, разделим все данные на тестовые и валидационные в соотношении 70:30 и обучим **Random Forest** с параметрами `n_estimators=100, max_depth=15, max_features=5`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sns.set(style = 'ticks', color_codes = True)\n", "\n", "df = shuffle(df)\n", "class_label = df.pop('Crime Type')\n", "X_train, X_valid, y_train, y_valid = train_test_split(df, class_label, test_size = .7)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%time\n", "model_rfc = RandomForestClassifier(n_estimators=100, max_depth=15, max_features=5, random_state=17, n_jobs=-1).\\\n", " fit(X_train, y_train)\n", "print(classification_report(y_valid, model_rfc.predict(X_valid)))\n", "plt.figure(figsize(16,8))\n", "pd.Series(model_rfc.feature_importances_, index=X_valid.columns).plot(kind='barh', color='g')\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Получается достаточно высокий уровень точности, но практически нет информации о значимости предикторов, так как все признаки находятся на одинаково небольшом уровне" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### CatBoost" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Попробуем другие алгоритмы, которые способны также показать степень важности предикторов. Следующим будет CatBoost. Используем его без преднасроенных гиперпараметров" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from catboost import CatBoostClassifier" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "model_cbc = CatBoostClassifier().fit(X_train, y_train)\n", "\n", "print(classification_report(y_valid, model_cbc.predict(X_valid)))\n", "pd.Series(model_cbc.get_feature_importance(X_valid, y_valid), index = X_valid.columns)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Из полученных результатов видно, что больший вес отдается признаку `Weapon`, хотя в `Random Forest` признак с наибольшим весом - `Victim Age`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### XGBoost" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Для сравнения проделаем аналогичную операцию с **XGBoost**" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import xgboost as xgb" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "model_xgb = xgb.XGBClassifier().fit(X_train, y_train)\n", "\n", "print(classification_report(y_valid, model_xgb.predict(X_valid)))\n", "importance = model_xgb.booster().get_score(importance_type = 'gain')\n", "pd.Series(list(importance.values()), index = importance.keys())\n", "\n", "xgb.plot_importance(model_xgb, color='green')\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Здесь также видно, что самым значимым признаком является `Weapon`, после которого почти сразу же следует `Relationship`, что выглядит вполне логично\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### KNeighborsClassifier" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В задачах анализа поведенческих факторов часто используется алгоритм ближайших соседей. Это связано с тем, что расстояние (например, евклидова метрика) между двумя векторами похожих людей будет близким. Логично предположить, что люди одной группы набирают похожее количество баллов, возможно, формируя несколько кластеров. \n", "\n", "Данный алгоритм не позволяет показывать степень важности предикторов. Но в небольших наборах данных обычно вручную удаляются признаки, а ддалее смотрят на качество обучения модели без него.\n", "\n", "Алгоритм ближайших соседей весьма чувствителен к разной природе данных (и к выбросам), следовательно, аналогично алгоритмам на основе нейронных сетей или методу опорных векторов требует предварительную нормализацию" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from sklearn.preprocessing import StandardScaler\n", "from sklearn.neighbors import KNeighborsClassifier\n", "\n", "scaler = StandardScaler()\n", "\n", "X_train_scaled = scaler.fit_transform(X_train)\n", "X_valid_scaled = scaler.fit_transform(X_valid)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "model_knn = KNeighborsClassifier(n_neighbors=7, n_jobs=-1).fit(X_train_scaled, y_train)\n", "print(classification_report(y_valid, model_knn.predict(X_valid_scaled)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Tensorflow" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "Ради эксперимента посмотрим на результат алгоритма на основе нейронных сетей (указано 3000 шагов для ускорения расчетов)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%time\n", "import tensorflow as tf\n", "\n", "def get_train_inputs():\n", " xr = tf.constant(X_train_scaled)\n", " yr = tf.constant(y_train)\n", " return xr, yr\n", "\n", "def get_test_inputs():\n", " xr = tf.constant(X_valid_scaled)\n", " yr = tf.constant(y_valid)\n", " return xr, yr\n", "\n", "model_tf = tf.contrib.learn.DNNClassifier(feature_columns = [tf.contrib.layers.real_valued_column('', dimension = 4)],\n", " hidden_units = [10, 20, 10], n_classes = 2, model_dir = 'tmp_dir_tensorflow').\\\n", " fit(input_fn = get_train_inputs, steps=3000)\n", "\n", "predictions = list(model_tf.predict(input_fn = get_test_inputs))\n", "print(classification_report(y_valid, predictions))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Выводы" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "Теперь можно перейти к выводам: \n", " \n", "в результате данного исследования было выявлено, что по представленному вектору можно с достаточно высокой степенью точности назвать правильный класс наблюдения, но все же недостаточной, если речь идет о том, чтобы быть посаженным на несколько лет. Поэтому в здачах подобного рода надо большое внимание уделить сбору данных, а также корректной интерпретации.\n", "\n", "Были рассмотрены алгоритмы\n", "\n", "- RandomForestClassifier\n", "- CatBoostClassifier\n", "- xgboost\n", "- KNeighborsClassifier\n", "- tensorflow\n", "\n", "Что касается признаков, то удалось выделить те, которые имеют наибольший вес из имеющихся, но все же для такого исследования признаков недостаточно. \n", "Если бы была возможность, то я бы еще точно добавила информацию об образовании, о семье подозреваемого, а также ответы на специально подобранные вопросы в зависимости от характера преступления. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "**Если у вас есть вопросы, критика или вы хотите поделиться своими мыслями на этот счет, то в slack вы можете меня найти как @elenadevyataykina**" ] } ], "metadata": { "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.1" } }, "nbformat": 4, "nbformat_minor": 2 }