Как набрать 0,8134 балла в Titanic Kaggle Challenge

машинное обучение

оригинал:Ахмед приносит ESB ES.com/how-to-birth или…

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

Я играл с набором данных Titanic и недавно получил оценку точности 0,8134 в общедоступной таблице лидеров. Когда я пишу это, я вхожу в 4% лучших пользователей Kaggler.

png

Этот пост поделится моим решением.

Чтобы сделать это руководство более «академическим», чтобы каждый мог извлечь из него пользу, я сначала начну с исследовательского анализа данных (EDA), затем перейду к разработке признаков и, наконец, представлю созданную мной прогностическую модель.

В этой записной книжке Jupyter я буду использовать Python на каждом уровне конвейера.

Основные библиотеки, описанные в этом руководстве:

  • PandasДля манипулирования данными и доступа (приема)
  • Matplotlibиseabornдля визуализации данных
  • Numpyдля вычислений многомерных массивов
  • sklearnдля машинного обучения и прогнозного моделирования

Процесс установки

Очень простой способ установить эти пакеты — загрузить и установитьConda, который представляет собой дистрибутив, который включает в себя все вышеперечисленные пакеты. Этот дистрибутив доступен для всех платформ (Windows, Linux и Mac OSX).

обращать внимание

Это мой первый опыт в качестве блогера и специалиста по машинному обучению.

Если у вас есть вопросы о коде или предположениях, которые я сделал, пожалуйста, не стесняйтесь комментировать в разделе комментариев ниже.

Если у вас также есть предложения по улучшению ноутбука, пожалуйста, свяжитесь со мной.

Этот урок доступен в моемgithubнашел в аккаунте.

Примечание переводчика: этот перевод находится наqiwihuiВниз.

Надеюсь, у вас все настроено на компьютере. Давайте начнем.

I - Исследовательский анализ данных

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

В этом разделе мы сделаем четыре вещи.

  • Извлечение данных: мы загрузим набор данных и сначала посмотрим на него.
  • Очистить: мы заполним пропущенные значения.
  • График: мы создадим несколько интересных графиков, которые (надеюсь) выявят корреляции и скрытые идеи в данных.
  • Предположения: мы будем делать предположения из графика.

Мы немного изменили стиль этого блокнота, чтобы рисунок располагался по центру.

from IPython.core.display import HTML
HTML("""
<style>
.output_png {
    display: table-cell;
    text-align: center;
    vertical-align: middle;
}
</style>
""");

Импорт полезных пакетов.

%matplotlib inline

import warnings
warnings.filterwarnings('ignore')
warnings.filterwarnings('ignore', category=DeprecationWarning)

import pandas as pd
pd.options.display.max_columns = 100

from matplotlib import pyplot as plt
import numpy as np

import seaborn as sns

import pylab as plot
params = {
    'axes.labelsize': "large",
    'xtick.labelsize': 'x-large',
    'legend.fontsize': 20,
    'figure.dpi': 150,
    'figure.figsize': [25, 7]
}
plot.rcParams.update(params)

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

Мы увидим, как этот процесс осуществляется в конце этой статьи.

Теперь давайте начнем загружать тренировочный набор.

data = pd.read_csv('./data/train.csv')
print data.shape
(891, 12)

у нас есть:

  • 891 строка
  • 12 столбцов

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

data.head()
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 0 PC 17599 71.2833 C85 C
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S
3 4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 113803 53.1000 C123 S
4 5 0 3 Allen, Mr. William Henry male 35.0 0 0 373450 8.0500 NaN S

Survivedстолбеццелевая переменная. еслиSurvivedНа 1 пассажир выжил, иначе он мертв. Это переменная, которую мы хотим предсказать.

Другие переменные описывают пассажиров. Они естьособенность.

  • PassengerId: Удостоверение личности каждого пассажира на борту
  • Pclass: Пассажирский класс. Имеет три возможных значения: 1, 2, 3 (первая, вторая и третья категории).
  • Name: имя пассажира
  • Sex:Пол
  • Age:возраст
  • SibSp: Количество братьев, сестер и супругов, путешествующих с пассажиром
  • Parch: Количество родителей и детей, путешествующих с пассажирами
  • Ticket: номер билета
  • Fare:транспортные расходы
  • Cabin: номер каюты
  • Embarked: здесь описываются три возможных места на Титанике, где люди садились на борт. Три возможных значения S, C, Q

Pandas позволяет выполнять сложные и простые статистические описания числовых функций. Это можно использоватьdescribeметод завершен.

data.describe()
PassengerId Survived Pclass Age SibSp Parch Fare
count 891.000000 891.000000 891.000000 714.000000 891.000000 891.000000 891.000000
mean 446.000000 0.383838 2.308642 29.699118 0.523008 0.381594 32.204208
std 257.353842 0.486592 0.836071 14.526497 1.102743 0.806057 49.693429
min 1.000000 0.000000 1.000000 0.420000 0.000000 0.000000 0.000000
25% 223.500000 0.000000 2.000000 20.125000 0.000000 0.000000 7.910400
50% 446.000000 0.000000 3.000000 28.000000 0.000000 0.000000 14.454200
75% 668.500000 1.000000 3.000000 38.000000 1.000000 0.000000 31.000000
max 891.000000 1.000000 3.000000 80.000000 8.000000 6.000000 512.329200

countпеременный дисплейAge177 значений отсутствуют в столбце.

Одно из решений — заполнить нули средним возрастом. Мы также могли бы использовать средний возраст для оценки, но медиана более устойчива к выбросам.

data['Age'] = data['Age'].fillna(data['Age'].median())

Давайте посмотрим на результаты.

data.describe()
PassengerId Survived Pclass Age SibSp Parch Fare
count 891.000000 891.000000 891.000000 891.000000 891.000000 891.000000 891.000000
mean 446.000000 0.383838 2.308642 29.361582 0.523008 0.381594 32.204208
std 257.353842 0.486592 0.836071 13.019697 1.102743 0.806057 49.693429
min 1.000000 0.000000 1.000000 0.420000 0.000000 0.000000 0.000000
25% 223.500000 0.000000 2.000000 22.000000 0.000000 0.000000 7.910400
50% 446.000000 0.000000 3.000000 28.000000 0.000000 0.000000 14.454200
75% 668.500000 1.000000 3.000000 35.000000 1.000000 0.000000 31.000000
max 891.000000 1.000000 3.000000 80.000000 8.000000 6.000000 512.329200

Идеально.

Теперь построим несколько графиков. Давайте посмотрим на выживание с точки зрения пола.

data['Died'] = 1 - data['Survived']
data.groupby('Sex').agg('sum')[['Survived', 'Died']].plot(kind='bar', figsize=(25, 7),
                                                          stacked=True, colors=['g', 'r']);

png

Похоже, что пассажиры-мужчины чаще умирают. Давайте нарисуем ту же фигуру, но в масштабе.

data.groupby('Sex').agg('mean')[['Survived', 'Died']].plot(kind='bar', figsize=(25, 7), 
                                                           stacked=True, colors=['g', 'r']);

png

Гендерная переменная, по-видимому, является дискриминационной чертой. У женщин больше шансов выжить.

Теперь давайте свяжем выживаемость с возрастной переменной.

fig = plt.figure(figsize=(25, 7))
sns.violinplot(x='Sex', y='Age', 
               hue='Survived', data=data, 
               split=True,
               palette={0: "r", 1: "g"}
              );

png

Как мы видим на диаграмме выше и подтверждено:

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

Теперь мы видим:

  • Выживаемость возрастных пассажиров мужского пола:

    • Молодые самцы, как правило, выживают * Погибло большое количество пассажиров в возрасте от 20 до 40 лет.
  • Возраст не оказывает прямого влияния на выживаемость женщин.

Следующий сюжет для скрипки подтверждает, что моряки и капитаны соблюдают старый кодекс поведения в случае угрозы:Женщины и дети в первую очередь!".

png

правильно?

Теперь давайте сосредоточимся на стоимости проезда на одного пассажира и посмотрим, как это влияет на выживаемость.

figure = plt.figure(figsize=(25, 7))
plt.hist([data[data['Survived'] == 1]['Fare'], data[data['Survived'] == 0]['Fare']], 
         stacked=True, color = ['g','r'],
         bins = 50, label = ['Survived','Dead'])
plt.xlabel('Fare')
plt.ylabel('Number of passengers')
plt.legend();

png

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

Хорошо, это нормально. Теперь объединим возраст, стоимость проезда и выживание на одном графике.

plt.figure(figsize=(25, 7))
ax = plt.subplot()

ax.scatter(data[data['Survived'] == 1]['Age'], data[data['Survived'] == 1]['Fare'], 
           c='green', s=data[data['Survived'] == 1]['Fare'])
ax.scatter(data[data['Survived'] == 0]['Age'], data[data['Survived'] == 0]['Fare'], 
           c='red', s=data[data['Survived'] == 0]['Fare']);

png

Размер круга пропорционален стоимости проезда.

По оси абсцисс указан возраст, а по оси ординат — стоимость проезда.

Мы можем наблюдать разные кластеры:

  1. Большая зеленая точка между x = 20 и x = 45: взрослый с самым высоким тарифом.
  2. Маленькая красная точка между x = 10 и x = 45, взрослые низшего класса на борту.
  3. Маленькие плотные точки между x = 0 и x = 7: это спасенные дети.

На самом деле тарифы связаны с категориями, которые мы видим в таблице ниже.

ax = plt.subplot()
ax.set_ylabel('Average fare')
data.groupby('Pclass').mean()['Fare'].plot(kind='bar', figsize=(25, 7), ax = ax);

png

Теперь давайте посмотрим, как место посадки влияет на выживание.

fig = plt.figure(figsize=(25, 7))
sns.violinplot(x='Embarked', y='Fare', hue='Survived', data=data, split=True, palette={0: "r", 1: "g"});

png

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

Мы также видим, что это происходит в точке посадки S вместо точки посадки Q.

Теперь давайте остановим исследование данных и перейдем к следующему разделу.

II - Разработка функций

В предыдущей части мы исследовали данные и обнаружили несколько интересных корреляций.

В этой части мы увидим, как эти переменные обрабатываются и преобразуются, чтобы данные стали управляемыми алгоритмами машинного обучения.

Мы также создадим или «разработаем» другие функции, полезные при построении модели.

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

Для большей наглядности мы разбросали код по отдельным функциям.

Но сначала давайте определим функцию печати, которая утверждает, была ли обработана функция.

def status(feature):
    print 'Processing', feature, ': ok'

Скачать данные

Уловка, чтобы начать задачу машинного обучения, состоит в том, чтобы добавить обучающий набор к тестовому набору вместе.

Мы будем использовать обучающий набор для разработки признаков, чтобы предотвратить утечку информации. Затем мы добавляем эти переменные в тестовый набор.

Давайте загрузим обучающие и тестовые наборы и объединим их.

def get_combined_data():
    # reading train data
    train = pd.read_csv('./data/train.csv')

    # reading test data
    test = pd.read_csv('./data/test.csv')

    # extracting and then removing the targets from the training data 
    targets = train.Survived
    train.drop(['Survived'], 1, inplace=True)


    # merging train data and test data for future feature engineering
    # we'll also remove the PassengerID since this is not an informative feature
    combined = train.append(test)
    combined.reset_index(inplace=True)
    combined.drop(['index', 'PassengerId'], inplace=True, axis=1)

    return combined
combined = get_combined_data()

Давайте посмотрим на размерность данных.

print combined.shape
(1309, 10)

Обучающая и тестовая выборки объединены. Вы можете заметить, что общее количество строк (1309) является точной суммой количества строк в обучающем и тестовом наборах.

combined.head()
Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S
1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 0 PC 17599 71.2833 C85 C
2 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S
3 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 113803 53.1000 C123 S
4 3 Allen, Mr. William Henry male 35.0 0 0 373450 8.0500 NaN S

Извлечь титул пассажира

Глядя на имена пассажиров, можно задаться вопросом, что с ними делать, чтобы извлечь полезную информацию.

Если вы внимательно посмотрите на эти первые примеры:

  • Braund, Mr. Owen Harris
  • Heikkinen, Miss. Laina
  • Oliva y Ocana, Dona. Fermina
  • Peter, Master. Michael J

Вы заметите, что у каждого имени есть наименование! Это может быть просто Мисс или Миссис, но иногда это может быть и более сложно, как Мастер, Сэр или Дона. В этом случае мы можем ввести дополнительную информацию о социальном статусе, просто проанализировав наименование, извлекая титул и преобразовывая его в двоичную переменную.

Давайте посмотрим, как мы это сделаем в функции ниже.

Давайте сначала посмотрим, какие разные имена есть в тренировочном наборе.

titles = set()
for name in data['Name']:
    titles.add(name.split(',')[1].split('.')[0].strip())
print titles
set(['Sir', 'Major', 'the Countess', 'Don', 'Mlle', 'Capt', 'Dr', 'Lady', 'Rev', 'Mrs', 'Jonkheer', 'Master', 'Ms', 'Mr', 'Mme', 'Miss', 'Col'])
Title_Dictionary = {
    "Capt": "Officer",
    "Col": "Officer",
    "Major": "Officer",
    "Jonkheer": "Royalty",
    "Don": "Royalty",
    "Sir" : "Royalty",
    "Dr": "Officer",
    "Rev": "Officer",
    "the Countess":"Royalty",
    "Mme": "Mrs",
    "Mlle": "Miss",
    "Ms": "Mrs",
    "Mr" : "Mr",
    "Mrs" : "Mrs",
    "Miss" : "Miss",
    "Master" : "Master",
    "Lady" : "Royalty"
}

def get_titles():
    # we extract the title from each name
    combined['Title'] = combined['Name'].map(lambda name:name.split(',')[1].split('.')[0].strip())

    # a map of more aggregated title
    # we map each title
    combined['Title'] = combined.Title.map(Title_Dictionary)
    status('Title')
    return combined

Эта функция анализирует имя и извлекает наименование. Затем он сопоставляет наименования с категориями наименований. Мы выбираем:

  • Officer
  • Royalty
  • Mr
  • Mrs
  • Miss
  • Master

Давайте запустить его!

combined = get_titles()
Processing Title : ok
combined.head()
Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked Title
0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S Mr
1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 0 PC 17599 71.2833 C85 C Mrs
2 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S Miss
3 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 113803 53.1000 C123 S Mrs
4 3 Allen, Mr. William Henry male 35.0 0 0 373450 8.0500 NaN S Mr

Проверим правильность заполнения заголовка.

combined[combined['Title'].isnull()]
Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked Title
1305 1 Oliva y Ocana, Dona. Fermina female 39.0 0 0 PC 17758 108.9 C105 C NaN

В строке 1305 действительно есть значения NaN. На самом деле соответствующее имяOliva y Ocana, **Dona**. Fermina.

Этот заголовок не встречался в обучающем наборе данных.

Отлично, теперь у нас есть файл с именемTitleдополнительные столбцы для хранения этой информации.

Возраст обработки (Возраст)

Мы видели в первой частиAgeВ переменной отсутствуют 177 значений. Это большое число (около 13% набора данных). Простая замена их средним или медианным возрастом может быть не лучшим решением, поскольку возраст может варьироваться в зависимости от класса и класса пассажиров.

Чтобы понять почему, давайте сгруппируем наш набор данных по полу (Sex), титулу (Title) и классу пассажиров (Pclass) и рассчитаем средний возраст для каждого подмножества.

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

Количество возрастов, отсутствующих в учебном классе

print combined.iloc[:891].Age.isnull().sum()
177

Количество возрастов, отсутствующих в тестовом наборе

print combined.iloc[891:].Age.isnull().sum()
86
grouped_train = combined.iloc[:891].groupby(['Sex','Pclass','Title'])
grouped_median_train = grouped_train.median()
grouped_median_train = grouped_median_train.reset_index()[['Sex', 'Pclass', 'Title', 'Age']]
grouped_median_train.head()
Sex Pclass Title Age
0 female 1 Miss 30.0
1 female 1 Mrs 40.0
2 female 1 Officer 49.0
3 female 1 Royalty 40.5
4 female 2 Miss 24.0

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

Посмотрите столбец среднего возраста, чтобы узнать, как это значение основано наSex,PclassиTitleсоединить все вместе.

Например:

  • Если пассажиром была женщина из P-класса 1 и из королевской семьи, средний возраст составлял 40,5 лет.
  • Если пассажир мужчина из P-класса 3 со званием «мистер», средний возраст составляет 26 лет.

Давайте создадим функцию, которая заполняется на основе этих различных свойств.комбинацияОтсутствующий возраст в .

def fill_age(row):
    condition = (
        (grouped_median_train['Sex'] == row['Sex']) & 
        (grouped_median_train['Title'] == row['Title']) & 
        (grouped_median_train['Pclass'] == row['Pclass'])
    )
    return grouped_median_train[condition]['Age'].values[0]


def process_age():
    global combined
    # a function that fills the missing values of the Age variable
    combined['Age'] = combined.apply(lambda row: fill_age(row) if np.isnan(row['Age']) else row['Age'], axis=1)
    status('age')
    return combined
combined = process_age()
Processing age : ok

Идеально. Отсутствующий возраст был заменен.

Однако мы заметили, что в тарифе отсутствует 1 значение (Fare), два значения отсутствуют в месте посадки (Embarked) и много значений отсутствует в местоположении салона (Cabin). Мы займемся этими переменными позже.

Теперь мы имеем дело с именами.

def process_names():
    global combined
    # we clean the Name variable
    combined.drop('Name', axis=1, inplace=True)

    # encoding in dummy variable
    titles_dummies = pd.get_dummies(combined['Title'], prefix='Title')
    combined = pd.concat([combined, titles_dummies], axis=1)

    # removing the title variable
    combined.drop('Title', axis=1, inplace=True)

    status('names')
    return combined

Эта функция удаляетNameстолбец, мы его больше не используем, потому что мы создалиTitleСписок.

Затем мы кодируем значение предиката, используя фиктивное кодирование.

Вы можете узнать о виртуальном кодировании и о том, какPandasсделать это легко.

combined = process_names()
Processing names : ok
combined.head()
Pclass Sex Age SibSp Parch Ticket Fare Cabin Embarked Title_Master Title_Miss Title_Mr Title_Mrs Title_Officer Title_Royalty
0 3 male 22.0 1 0 A/5 21171 7.2500 NaN S 0 0 1 0 0 0
1 1 female 38.0 1 0 PC 17599 71.2833 C85 C 0 0 0 1 0 0
2 3 female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S 0 1 0 0 0 0
3 1 female 35.0 1 0 113803 53.1000 C123 S 0 0 0 1 0 0
4 3 male 35.0 0 0 373450 8.0500 NaN S 0 0 1 0 0 0

как вы видете :

  • Функция имени больше не используется.
  • Появится новая переменная (Title_X). Эти функции являются бинарными.
    • Например, если Title_Mr = 1, соответствующий титул — Mr.

Транспортные расходы

Оценим стоимость отсутствующего билета по средней цене билета, рассчитанной на обучающем наборе.

def process_fares():
    global combined
    # there's one missing fare value - replacing it with the mean.
    combined.Fare.fillna(combined.iloc[:891].Fare.mean(), inplace=True)
    status('fare')
    return combined

Эта функция заменяет отсутствующее значение тарифа средним значением.

combined = process_fares()
Processing fare : ok

Обращение с локациями Embarked (Embarked)

def process_embarked():
    global combined
    # two missing embarked values - filling them with the most frequent one in the train  set(S)
    combined.Embarked.fillna('S', inplace=True)
    # dummy encoding
    embarked_dummies = pd.get_dummies(combined['Embarked'], prefix='Embarked')
    combined = pd.concat([combined, embarked_dummies], axis=1)
    combined.drop('Embarked', axis=1, inplace=True)
    status('embarked')
    return combined

Эта функция использует наиболее часто используемыеEmbarkedзначение заменяет два недостающихEmbarkedценность.

combined = process_embarked()
Processing embarked : ok
combined.head()
Pclass Sex Age SibSp Parch Ticket Fare Cabin Title_Master Title_Miss Title_Mr Title_Mrs Title_Officer Title_Royalty Embarked_C Embarked_Q Embarked_S
0 3 male 22.0 1 0 A/5 21171 7.2500 NaN 0 0 1 0 0 0 0 0 1
1 1 female 38.0 1 0 PC 17599 71.2833 C85 0 0 0 1 0 0 1 0 0
2 3 female 26.0 0 0 STON/O2. 3101282 7.9250 NaN 0 1 0 0 0 0 0 0 1
3 1 female 35.0 1 0 113803 53.1000 C123 0 0 0 1 0 0 0 0 1
4 3 male 35.0 0 0 373450 8.0500 NaN 0 0 1 0 0 0 0 0 1

Положение кабины (Кабина)

train_cabin, test_cabin = set(), set()

for c in combined.iloc[:891]['Cabin']:
    try:
        train_cabin.add(c[0])
    except:
        train_cabin.add('U')

for c in combined.iloc[891:]['Cabin']:
    try:
        test_cabin.add(c[0])
    except:
        test_cabin.add('U')
print train_cabin
set(['A', 'C', 'B', 'E', 'D', 'G', 'F', 'U', 'T'])
print test_cabin
set(['A', 'C', 'B', 'E', 'D', 'G', 'F', 'U'])

У нас нет букв положения кабины в тестовом наборе, которых нет в обучающем наборе.

def process_cabin():
    global combined
    # replacing missing cabins with U (for Uknown)
    combined.Cabin.fillna('U', inplace=True)

    # mapping each Cabin value with the cabin letter
    combined['Cabin'] = combined['Cabin'].map(lambda c: c[0])

    # dummy encoding ...
    cabin_dummies = pd.get_dummies(combined['Cabin'], prefix='Cabin')
    combined = pd.concat([combined, cabin_dummies], axis=1)

    combined.drop('Cabin', axis=1, inplace=True)
    status('cabin')
    return combined

Эта функция будетNaNзначение заменяется на U (что означаетUnknow). тогда это будет каждыйCabinЗначение отображается на первую букву. Затем он снова кодирует значение класса, используя фиктивное кодирование.

combined = process_cabin()
Processing cabin : ok

Ну нет пропущенных значений.

combined.head()
Pclass Sex Age SibSp Parch Ticket Fare Title_Master Title_Miss Title_Mr Title_Mrs Title_Officer Title_Royalty Embarked_C Embarked_Q Embarked_S Cabin_A Cabin_B Cabin_C Cabin_D Cabin_E Cabin_F Cabin_G Cabin_T Cabin_U
0 3 male 22.0 1 0 A/5 21171 7.2500 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1
1 1 female 38.0 1 0 PC 17599 71.2833 0 0 0 1 0 0 1 0 0 0 0 1 0 0 0 0 0 0
2 3 female 26.0 0 0 STON/O2. 3101282 7.9250 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1
3 1 female 35.0 1 0 113803 53.1000 0 0 0 1 0 0 0 0 1 0 0 1 0 0 0 0 0 0
4 3 male 35.0 0 0 373450 8.0500 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1

Работа с сексом (секс)

def process_sex():
    global combined
    # mapping string values to numerical one
    combined['Sex'] = combined['Sex'].map({'male':1, 'female':0})
    status('Sex')
    return combined

Эта функция преобразует строковое значениеmaleиfemaleсопоставить с 1 и 0 соответственно.

combined = process_sex()
Processing Sex : ok

Обработка пассажирского класса (Pclass)

def process_pclass():

    global combined
    # encoding into 3 categories:
    pclass_dummies = pd.get_dummies(combined['Pclass'], prefix="Pclass")

    # adding dummy variable
    combined = pd.concat([combined, pclass_dummies],axis=1)

    # removing "Pclass"
    combined.drop('Pclass',axis=1,inplace=True)

    status('Pclass')
    return combined

Эта функция кодирует значение Pclass(1,2,3) с помощью фиктивного кодирования.

combined = process_pclass()
Processing Pclass : ok

Номер тикета обработки (тикет)

Давайте сначала посмотрим на различные префиксы номеров билетов в нашем наборе данных.

def cleanTicket(ticket):
    ticket = ticket.replace('.', '')
    ticket = ticket.replace('/', '')
    ticket = ticket.split()
    ticket = map(lambda t : t.strip(), ticket)
    ticket = list(filter(lambda t : not t.isdigit(), ticket))
    if len(ticket) > 0:
        return ticket[0]
    else:
        return 'XXX'
tickets = set()
for t in combined['Ticket']:
    tickets.add(cleanTicket(t))
print len(tickets)
37
def process_ticket():

    global combined

    # a function that extracts each prefix of the ticket, returns 'XXX' if no prefix (i.e the ticket is a digit)
    def cleanTicket(ticket):
        ticket = ticket.replace('.','')
        ticket = ticket.replace('/','')
        ticket = ticket.split()
        ticket = map(lambda t : t.strip(), ticket)
        ticket = filter(lambda t : not t.isdigit(), ticket)
        if len(ticket) > 0:
            return ticket[0]
        else:
            return 'XXX'


    # Extracting dummy variables from tickets:

    combined['Ticket'] = combined['Ticket'].map(cleanTicket)
    tickets_dummies = pd.get_dummies(combined['Ticket'], prefix='Ticket')
    combined = pd.concat([combined, tickets_dummies], axis=1)
    combined.drop('Ticket', inplace=True, axis=1)

    status('Ticket')
    return combined
combined = process_ticket()
Processing Ticket : ok

иметь дело с семьей

Эта часть включает в себя создание новой переменной на основе размера семьи (размер — это еще одна переменная, которую мы создали).

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

def process_family():

    global combined
    # introducing a new feature : the size of families (including the passenger)
    combined['FamilySize'] = combined['Parch'] + combined['SibSp'] + 1

    # introducing other features based on the family size
    combined['Singleton'] = combined['FamilySize'].map(lambda s: 1 if s == 1 else 0)
    combined['SmallFamily'] = combined['FamilySize'].map(lambda s: 1 if 2 <= s <= 4 else 0)
    combined['LargeFamily'] = combined['FamilySize'].map(lambda s: 1 if 5 <= s else 0)

    status('family')
    return combined

Эта функция представляет 4 новые функции:

  • FamilySize: Общее количество родственников, включая самого пассажира (его/ее).
  • Sigleton: логическая переменная, описывающая семьи размера = 1
  • SmallFamily: логическая переменная, описывающая семьи из 2
  • LargeFamily: логическая переменная, описывающая семейство 5
combined = process_family()
Processing family : ok
print combined.shape
(1309, 67)

В итоге мы получили 67 признаков.

combined.head()
Sex Age SibSp Parch Fare Title_Master Title_Miss Title_Mr Title_Mrs Title_Officer Title_Royalty Embarked_C Embarked_Q Embarked_S Cabin_A Cabin_B Cabin_C Cabin_D Cabin_E Cabin_F Cabin_G Cabin_T Cabin_U Pclass_1 Pclass_2 Pclass_3 Ticket_A Ticket_A4 Ticket_A5 Ticket_AQ3 Ticket_AQ4 Ticket_AS Ticket_C Ticket_CA Ticket_CASOTON Ticket_FC Ticket_FCC Ticket_Fa Ticket_LINE Ticket_LP Ticket_PC Ticket_PP Ticket_PPP Ticket_SC Ticket_SCA3 Ticket_SCA4 Ticket_SCAH Ticket_SCOW Ticket_SCPARIS Ticket_SCParis Ticket_SOC Ticket_SOP Ticket_SOPP Ticket_SOTONO2 Ticket_SOTONOQ Ticket_SP Ticket_STONO Ticket_STONO2 Ticket_STONOQ Ticket_SWPP Ticket_WC Ticket_WEP Ticket_XXX FamilySize Singleton SmallFamily LargeFamily
0 1 22.0 1 0 7.2500 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 1 0
1 0 38.0 1 0 71.2833 0 0 0 1 0 0 1 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 1 0
2 0 26.0 0 0 7.9250 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 1 0 0
3 0 35.0 1 0 53.1000 0 0 0 1 0 0 0 0 1 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 2 0 1 0
4 1 35.0 0 0 8.0500 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0

III - Модель

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

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

Мы будем использовать случайный лес. Random Froests продемонстрировали высокую эффективность в соревнованиях Kaggle.

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

Возвращаясь к нашему вопросу, теперь мы должны:

1. Разделите объединенный набор данных на обучающий набор и тестовый набор. 2. Используйте обучающий набор для построения прогностической модели. 3. Оцените модель, используя обучающую выборку. 4. Протестируйте модель с помощью тестового набора, создайте и выведите файл отправки.

Помните, мы должны повторять 2 и 3, пока не будет достигнута приемлемая оценка.

Давайте сначала импортируем пакеты, которые нам нужно использовать.

from sklearn.pipeline import make_pipeline
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble.gradient_boosting import GradientBoostingClassifier
from sklearn.feature_selection import SelectKBest
from sklearn.model_selection import StratifiedKFold
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import cross_val_score
from sklearn.feature_selection import SelectFromModel
from sklearn.linear_model import LogisticRegression, LogisticRegressionCV

Чтобы оценить нашу модель, мы будем использовать 5-кратную перекрестную проверку, поскольку это показатель, используемый в таблице лидеров соревнований.

Для этого мы определим небольшую оценочную функцию.

def compute_score(clf, X, y, scoring='accuracy'):
    xval = cross_val_score(clf, X, y, cv = 5, scoring=scoring)
    return np.mean(xval)

Восстановление обучающих и тестовых наборов из объединенного набора данных — простая задача.

def recover_train_test_target():
    global combined

    targets = pd.read_csv('./data/train.csv', usecols=['Survived'])['Survived'].values
    train = combined.iloc[:891]
    test = combined.iloc[891:]

    return train, test, targets
train, test, targets = recover_train_test_target()

Выбор функций

На данный момент мы предложили более 30 функций. Это число очень велико.

При разработке функций мы обычно склонны уменьшать размерность, выбирая «правильное» количество функций, которые охватывают основные функции.

На самом деле выбор функций дает много преимуществ:

  • Это уменьшает избыточность между данными
  • Ускоряет процесс обучения
  • уменьшает переоснащение

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

clf = RandomForestClassifier(n_estimators=50, max_features='sqrt')
clf = clf.fit(train, targets)

Давайте посмотрим на важность каждой функции.

features = pd.DataFrame()
features['feature'] = train.columns
features['importance'] = clf.feature_importances_
features.sort_values(by=['importance'], ascending=True, inplace=True)
features.set_index('feature', inplace=True)
features.plot(kind='barh', figsize=(25, 25))
<matplotlib.axes._subplots.AxesSubplot at 0x117ff2a10>

png

Как вы могли заметить, сTitle_Mr,Age,FareиSexКорреляция очень важна.

иPassenger_IdЕсть и важные корреляции.

Теперь давайте преобразуем наши обучающие и тестовые наборы в более компактный набор данных.

model = SelectFromModel(clf, prefit=True)
train_reduced = model.transform(train)
print train_reduced.shape
(891, 12)
test_reduced = model.transform(test)
print test_reduced.shape
(418, 12)

отличный! Теперь у нас намного меньше возможностей.

Посмотрим, будем ли мы использовать сокращенную или полную версию тренировочного набора.

Давайте попробуем другую базовую модель

logreg = LogisticRegression()
logreg_cv = LogisticRegressionCV()
rf = RandomForestClassifier()
gboost = GradientBoostingClassifier()

models = [logreg, logreg_cv, rf, gboost]
for model in models:
    print 'Cross-validation of : {0}'.format(model.__class__)
    score = compute_score(clf=model, X=train_reduced, y=targets, scoring='accuracy')
    print 'CV score = {0}'.format(score)
    print '****'
Cross-validation of : <class 'sklearn.linear_model.logistic.LogisticRegression'>
CV score = 0.818195097715
****
Cross-validation of : <class 'sklearn.linear_model.logistic.LogisticRegressionCV'>
CV score = 0.81818240172
****
Cross-validation of : <class 'sklearn.ensemble.forest.RandomForestClassifier'>
CV score = 0.808183171282
****
Cross-validation of : <class 'sklearn.ensemble.gradient_boosting.GradientBoostingClassifier'>
CV score = 0.824917697684
****

Настройка гиперпараметров

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

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

Чтобы узнать больше о случайных лесах, см.Ссылка на сайт.

Также мы будем использовать полный тренировочный набор.

# turn run_gs to True if you want to run the gridsearch again.
run_gs = False

if run_gs:
    parameter_grid = {
                 'max_depth' : [4, 6, 8],
                 'n_estimators': [50, 10],
                 'max_features': ['sqrt', 'auto', 'log2'],
                 'min_samples_split': [2, 3, 10],
                 'min_samples_leaf': [1, 3, 10],
                 'bootstrap': [True, False],
                 }
    forest = RandomForestClassifier()
    cross_validation = StratifiedKFold(n_splits=5)

    grid_search = GridSearchCV(forest,
                               scoring='accuracy',
                               param_grid=parameter_grid,
                               cv=cross_validation,
                               verbose=1
                              )

    grid_search.fit(train, targets)
    model = grid_search
    parameters = grid_search.best_params_

    print('Best score: {}'.format(grid_search.best_score_))
    print('Best parameters: {}'.format(grid_search.best_params_))

else:
    parameters = {'bootstrap': False, 'min_samples_leaf': 3, 'n_estimators': 50,
                  'min_samples_split': 10, 'max_features': 'sqrt', 'max_depth': 6}

    model = RandomForestClassifier(**parameters)
    model.fit(train, targets)

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

output = model.predict(test).astype(int)
df_output = pd.DataFrame()
aux = pd.read_csv('./data/test.csv')
df_output['PassengerId'] = aux['PassengerId']
df_output['Survived'] = output
df_output[['PassengerId','Survived']].to_csv('./predictions/gridsearch_rf.csv', index=False)

[БОНУС] Комбинируйте разные модели

Я сам не загружал коммиты на основе миксов моделей, но вот как вы можете это сделать:

trained_models = []
for model in models:
    model.fit(train, targets)
    trained_models.append(model)

predictions = []
for model in trained_models:
    predictions.append(model.predict_proba(test)[:, 1])

predictions_df = pd.DataFrame(predictions).T
predictions_df['out'] = predictions_df.mean(axis=1)
predictions_df['PassengerId'] = aux['PassengerId']
predictions_df['out'] = predictions_df['out'].map(lambda s: 1 if s >= 0.5 else 0)

predictions_df = predictions_df[['PassengerId', 'out']]
predictions_df.columns = ['PassengerId', 'Survived']
predictions_df.to_csv('./predictions/blending_base_models.csv', index=False)

Чтобы получить хорошее сочетание представлений, базовые модели должны быть разными, а их корреляции некоррелированными.

IV - Заключение

В этой статье мы исследуемKaggleПринес нам интересный набор данных.

Мы рассмотрели основы пайплайна науки о данных:

  • Исследование и визуализация данных: первый шаг к формулированию гипотез
  • очистка данных
  • разработка функций
  • Выбор функций
  • Настройка гиперпараметров
  • Отправить
  • смешивание

Если вы хотите протестировать и поиграть с ним, вы можете скачать этот блог в виде блокнота:мой репозиторий на гитхабе

Примечание переводчика: адрес этого китайского перевода:репозиторий qiwihui на github

Много было написано об этой проблеме, так что явно есть место для улучшения.

Вот мои рекомендуемые следующие шаги:

  • Извлечение большего количества данных и, в конечном итоге, создание новых функций.
  • Попробуйте разные модели: логистическая регрессия, Gradient Boosted Tree, XGboost и т. д.
  • Попробуйте интегрированные навыки обучения (с накоплением)
  • Запуск фреймворка auto-ML

Я был бы очень рад, если бы вы могли найти способ улучшить мое решение. Это позволяет мне обновлять статью, определенно отдавая вам должное. Так что не стесняйтесь оставлять комментарии.