оригинал:Ахмед приносит ESB ES.com/how-to-birth или…
Хостинг от KaggleИспытание на выживание на Титаникеэто соревнование, цель которого состоит в том, чтобы предсказать, будет ли конкретный пассажир жить или умереть, на основе набора переменных, описывающих пассажира, таких как его возраст, пол или класс пассажира на борту.
Я играл с набором данных Titanic и недавно получил оценку точности 0,8134 в общедоступной таблице лидеров. Когда я пишу это, я вхожу в 4% лучших пользователей Kaggler.
Этот пост поделится моим решением.
Чтобы сделать это руководство более «академическим», чтобы каждый мог извлечь из него пользу, я сначала начну с исследовательского анализа данных (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
переменный дисплейAge
177 значений отсутствуют в столбце.
Одно из решений — заполнить нули средним возрастом. Мы также могли бы использовать средний возраст для оценки, но медиана более устойчива к выбросам.
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']);
Похоже, что пассажиры-мужчины чаще умирают. Давайте нарисуем ту же фигуру, но в масштабе.
data.groupby('Sex').agg('mean')[['Survived', 'Died']].plot(kind='bar', figsize=(25, 7),
stacked=True, colors=['g', 'r']);
Гендерная переменная, по-видимому, является дискриминационной чертой. У женщин больше шансов выжить.
Теперь давайте свяжем выживаемость с возрастной переменной.
fig = plt.figure(figsize=(25, 7))
sns.violinplot(x='Sex', y='Age',
hue='Survived', data=data,
split=True,
palette={0: "r", 1: "g"}
);
Как мы видим на диаграмме выше и подтверждено:
- Женщины имеют более высокую выживаемость, чем мужчины, как показано на более крупной зеленой гистограмме для женщин.
Теперь мы видим:
-
Выживаемость возрастных пассажиров мужского пола:
- Молодые самцы, как правило, выживают * Погибло большое количество пассажиров в возрасте от 20 до 40 лет.
-
Возраст не оказывает прямого влияния на выживаемость женщин.
Следующий сюжет для скрипки подтверждает, что моряки и капитаны соблюдают старый кодекс поведения в случае угрозы:Женщины и дети в первую очередь!".
правильно?
Теперь давайте сосредоточимся на стоимости проезда на одного пассажира и посмотрим, как это влияет на выживаемость.
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();
Пассажиры с более низкими тарифами чаще умирают. Другими словами, пассажиры с более дорогими билетами и, следовательно, более важным социальным статусом, похоже, спасаются в первую очередь.
Хорошо, это нормально. Теперь объединим возраст, стоимость проезда и выживание на одном графике.
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']);
Размер круга пропорционален стоимости проезда.
По оси абсцисс указан возраст, а по оси ординат — стоимость проезда.
Мы можем наблюдать разные кластеры:
- Большая зеленая точка между x = 20 и x = 45: взрослый с самым высоким тарифом.
- Маленькая красная точка между x = 10 и x = 45, взрослые низшего класса на борту.
- Маленькие плотные точки между 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);
Теперь давайте посмотрим, как место посадки влияет на выживание.
fig = plt.figure(figsize=(25, 7))
sns.violinplot(x='Embarked', y='Fare', hue='Survived', data=data, split=True, palette={0: "r", 1: "g"});
Похоже, что в точке посадки 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>
Как вы могли заметить, с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
Я был бы очень рад, если бы вы могли найти способ улучшить мое решение. Это позволяет мне обновлять статью, определенно отдавая вам должное. Так что не стесняйтесь оставлять комментарии.