Предисловие автора
Данную статью я решил написать в качестве своей первой основательной работы по анализу данных и созданию предиктивной модели на базе нейронных сетей. Все упоминающиеся в тексте файлы можно найти в репозитории этой работы на GitHub.
В данной статье исследованы данные пассажиров Титаника (предоставленные в рамках ML-соревнования на kaggle.com [1]), сделаны и проверены предположения о влиянии определённых факторов на вероятность человека выжить в той катастрофе. Анализ данных сопровождается примерами кода на Python, с использованием пакета Pandas. Построена и обучена модель нейронной сети, предсказывающая вероятность человека выжить в катастрофе с точностью 0.78 на тестовых данных из [1]. Модель построена на базе фреймворка pyTorch.
Содержание
Введение
15 апреля 1912 года произошло крушение парохода «Титаник», став одной из самых значимых катастроф в истории человечества. За сто лет с момента затопления лайнера, в мире накопилось множество данных и работ по этой теме. На сайте Kaggle.com существует соревнование [1], в основе которого лежат данные о пассажирах с Титаника. Катастрофа сложилась так, что некоторые пассажиры выживали с большим шансом, чем другие, и это отражено в данных, полученых в рамках соревнования.
Целью данной работы является создание предиктивной модели, которая на основе данных о пассажире сможет предсказать, выжил он после крушения Титаника или нет. В первом разделе будет рассмотрен разведовательный анализ исходных данных пассажиров, а также проектирование признаков для последующего использования в модели машинного обучения. Второй раздел включает в себя подготовку данных, описание структуры используемой модели, результаты обучения модели.
Анализ данных
Целью раздела "Анализ данных" является разведовательный анализ данных, выявление и проверка гипотез, инжиниринг признаков (feature engineering) для модели машинного обучения.
Все примеры кода и графика продублированы из файла Data_Investigation.ipynb
.
Исходные данные
Исходные данные представлены в виде .csv файла, образующего таблицу (891 строка, 12 столбцов), представленную на рисунке 1 ниже. Мы можем наблюдать следующие данные для каждого из пассажиров: класс, полное имя, пол, возраст, количество родственников супруг+братья\сестры, количество родственников родители+дети, номер билета, стоимость билета, номер каюты, порт отправления, выжил пассажир или нет.
import pandas as pd
# Считаем файл с данными
df = pd.read_csv('train.csv')
# Взглянем на данные!
df
Сразу отметим следующие особенности этих данных (виузализацию см. в файле Data_Investigation.ipynb
):
Пропущено около 20% данных о возрасте пассажиров.
Пропущено около 80% данных о каютах пассажиров.
У 15 пассажиров нулевая стоимость билетов.
У 2 пассажиров отсутствует порт отправления.
У 4 пассажиров нет номеров билетов.
Факторы, влияющие на шансы выжить
За более чем сто лет с момента катастрофы, было проведено множество исследований, в том числе статистических. В [2] и [3] описаны ключевые моменты, влияющие на выживаемость пассажиров:
Пол (выжило 75% женщин и 16% мужчин [2]).
Возраст (выжило 52% детей [2]).
Класс каюты (выжило из 1 класса 62%, из 2 - 41%, из 3 - 25% [2]).
Первое и второе обусловлено тем, как проводилась эвакуация пассажиров. По приказу капитана в первую очередь в шлюпки сажали женщин и детей [4, 5].
Влияние класса на выживаемость выражалось в общей привелегированности 1 класса над вторым и третьим, и второго класса над третьим [2]. Конкретно стоить отметить, что:
Каюты 1 класса располагались ближе всего к палубе [2].
Первый класс обслуживали 2 лифта, второй класс - 1 лифт, третий класс лифты не обслуживали вообще [2].
На пароходе не было системы оповещения, поэтому о необходимости эвакуироваться людям сообщали стюарды. Причем в 1 классе на одного стюарда приходилось всего несколько кают, а во 2 и 3 - много больше [7]. В первом классе стюарды имели возможность лично помочь и выпроводить каждого пассажира [7], а в третьем классе стюарды просто выбивали двери в каюты [6], и сообщали о том, что нужно выбираться на палубу.
Далее рассмотрено влияние отдельных факторов на выживаемость в рамках исследуемых данных.
Пол и класс каюты
Посчитаем общее количество и количество выживших для мужчин и женщин, визуализируем на диаграмме.
import matplotlib.pyplot as plt
import numpy as np
# Посчитаем общее количество мужчин и женщин, а также количество выживших
males_total = len(df.loc[(df['Sex'] == 'male')])
females_total = len(df.loc[(df['Sex'] == 'female')])
males_survived = len(df.loc[((df['Sex'] == 'male') & (df['Survived'] == 1))])
females_survived = len(df.loc[((df['Sex'] == 'female') & (df['Survived'] == 1))])
#Визуализируем
survivors_counts = {
'Выжили': [males_survived, females_survived],
'Погибли': [males_total-males_survived, females_total-females_survived]
}
fig, ax = plt.subplots()
bottom = np.zeros(2)
for key, count in survivors_counts.items():
p = ax.bar(('Мужчины', 'Женщины'), count, width=0.6, label=key, bottom=bottom)
bottom += count
ax.bar_label(p, label_type='center')
ax.set_title('Распределение выживших в зависимости от пола')
ax.set_ylabel('Количество человек')
ax.legend()
plt.show()
В процентном соотношении получаем числа, аналогичные упомянутым ранее [2, 3]:
Выжило мужчин: 18.89%
Выжило женщин: 74.2%
Построим аналогичную диаграмму для различных классов каюты (код см. в файле Data_Investigation.ipynb
).
В процентном соотношении:
Выжило из 1 класса: 62.96%
Выжило из 2 класса: 47.28%
Выжило из 3 класса: 24.24%
Таким образом, пол и класс каюты будут одними из ключевых факторов (и признаков для модели), влияющих на вероятность выжить.
Возраст
Рассмотрим распределение пассажиров по возрастам, а также распределение выживших, мужчин и женщин по возрастам.
from collections import Counter
#Выберем списки пассжиров в отдельные группы
survivors = df.loc[(df['Survived'] == 1)]
male_survivors = df.loc[((df['Sex'] == 'male') & (df['Survived'] == 1))]
female_survivors = df.loc[((df['Sex'] == 'female') & (df['Survived'] == 1))]
#Число возрастов
num_of_ages = len(dict(Counter(survivors['Age'])).keys())
#Визуализируем
fig, axs = plt.subplots(2,2)
fig.set_figwidth(20)
fig.set_figheight(10)
names = [['Все пассажиры', 'Выжившие'], ['Выжившие мужчины', 'Выжившие женщины']]
for i, surv in enumerate([[df, survivors], [male_survivors, female_survivors]]):
for j, subsurv in enumerate(surv):
axs[i][j].hist(subsurv['Age'], bins=num_of_ages)
axs[i][j].set_title(names[i][j])
axs[i][j].set_xlabel('Возраст, лет')
axs[i][j].set_ylabel('Количество человек')
axs[i][j].set_ylim(0,30)
axs[i][j].set_xlim(0,70)
plt.show()
На каждой диаграмме можно заметить 2 характерные моды: одна соответствует детям, а другая людям с возрастом 20-30 лет. Причем на всех диаграммах распределение сохраняет свой характер, хоть при этом и смещается μ, падают амплитуды. Видно, что в зависимости от возраста количество выживших значительно разнится, поэтому возраст будет важным признаком при построении модели.
Попробуем также убедиться, что большая часть детей спаслись.
import numpy as np
children = df.loc[((df['Age'] < np.float64(18.0)) & (df['Age'] > 0))]
survivors_children = df.loc[((df['Age'] < np.float64(18.0)) & (df['Survived'] == 1) & (df['Age'] > 0))]
print(f'Пасажиров до 18 лет спаслось {round(len(survivors_children)*100/len(children), 2)}%')
Пасажиров до 18 лет спаслось 53.98%.\
Действительно, более половины детей спаслось. Причём, если мы еще раз посмотрим на диаграммы, то видно, что больше всего спаслось детей до 5 лет.
df.loc[((df['Survived'] == 1) & (df['Age'] > 0) & (df['Age'] < 18))].Age.hist()
Имена и возраст
Имена пассажиров содержат характерные для 1912 годп приставки, такие как "Mr", "Mrs", "Miss", "Sir", "Master" и другие. Эти приставки отражают информацию о возрасте и статусе человека [8]. Автор [9] в ходе своего анализа данных пассажиров "Титаника" подтвердил, что статус человека, отражённый в имени, тесно коррелирует с возрастом, и эту взаимосвязь можно использовать для определения возраста пассажиров, возраст которых в исходных данных пропущен. Отобразим зависимость возраста от титула в виде набора коробчатых диаграмм.
#Перечень титулов
titles = ("Capt.","Col.","Major.","Sir.","Lady.","Rev.","Dr.","Don.","Jonkheer.","Countess.","Mrs.","Ms.","Mr.","Mme.","Mlle.","Miss.","Master.")
#Создадим список титулов для каждого пассажира
titled_names = []
for name in df.Name:
for title in titles:
if title in name.split(' '):
titled_names.append(title)
break
#Добавим в датафрейм новый столбец - Титул
df.insert(12, 'Title', titled_names)
#Создадим словарь с парами "титул: список возрастов"
ages = dict.fromkeys(titles, [])
for i, title in enumerate(df.Title):
if not pd.isna(df.Age[i]):
ages[title] = ages[title] + [df.Age[i]]
#Визуализируем
fig, ax = plt.subplots(figsize=(15,5))
ax.boxplot(ages.values(), labels=titles, vert=True)
ax.set_ylabel('Возраст')
plt.show()
Прежде, чем восстанавливать отсутствующие возраста, рассмотрим отдельно пассажиров без указания возраста. Возраст людей может быть неизвестен не только потому, что они его изначально не сообщили, но и потому что погибли и не смогли сообщить его после катастрофы. Посмотрим на соотношение погибших и выживших среди этих пассажиров, а также на соотношение мужчин и женщин среди пассажиров без возраста.
noage = df.loc[(pd.isna(df['Age']))]
fig, ax = plt.subplots(1, 2, figsize=(10,5))
ax[0].hist(noage.Survived, bins=2, cumulative=-1)
ax[1].hist(noage.Sex, bins=2)
ax[0].set_ylabel('Кол-вол выживших')
Можно увидеть, что среди пассажиров без возраста больше погибших потому, что среди них больше мужчин. Однако разумно будет добавить в данные признак have_age
, отражающий наличие\отсутствие указания возраста пассажира, поскольку у мужчины без указания возраста шансы погибнуть будут еще больше, чем просто у мужчины, это должно прибавить точности модели.
Восстановим возраста, используя медиану среди пассажиров с соответствующим титулом.
for i, age in enumerate(df.Age):
if pd.isna(age):
df.Age[i] = np.median(ages[df.Title[i]])
Выживаемость в зависимости от титула
Автор [9] также предложил распределить титулы в 5 групп: Aristocratic, Mr, Mrs, Miss и Master (объединяя вместе родственные [8] группы), а затем посмотреть на выживаемость среди пассажиров с разным титулом.
# Группы для объединения
aristocratic = ("Capt.", "Col.", "Don.", "Dr.",
"Jonkheer.", "Lady.", "Major.",
"Rev.", "Sir.", "Countess.")
mrs = ("Ms.")
miss = ("Mlle.", 'Mme.')
# Объединяем титулы
for i, title in enumerate(df.Title):
if title in aristocratic:
df.Title[i] = 'Aristocratic.'
elif title in miss:
df.Title[i] = 'Miss.'
elif title in mrs:
df.Title[i] = 'Mrs.'
# В данном случае будет удобно посмотреть на долю выживших, так как количество людей в каждом из титулов значительно разнится
title_survive_percent = dict.fromkeys(set(df.Title), None)
for title in title_survive_percent.keys():
title_survive_percent[title] = len(df.loc[((df['Title'] == title) & (df['Survived'] == 1))]) / len(df.loc[(df['Title'] == title)])
#Визуализируем
fig, ax = plt.subplots()
ax.bar(title_survive_percent.keys(), title_survive_percent.values())
ax.set_ylabel('Доля выживших')
ax.set_title('Выживаемость в зависимости от титула пассажира')
plt.show()
Видно, что титул даёт информацию о вероятности выжить, так что он будет иметь значение при построении модели.
Номер каюты
Среди номеров каюты пропущены данные для 80% пассажиров. Восстановить эти данные не представляется возможным. Для тех же пассажиров, для которых номер каюты известен, можно было бы извлечь номер палубы и на каком борту была каюта (буква в номере соответствует палубе, нечетные номера соответствуют левому борту [7]). Однако, учитывая малое количество данных, это существенно не повлияет на точность модели [9].
Сведения о каютах пассажиров стали известны благодаря списку, найденному на теле погибшего стюарта Герберта Кейва, причем в список были включены только пассажиры первого класса [10]. Это означает, что значение может иметь само наличие или отсутствие данных о каюте пассажира.
# Выделим пассажиров в группы
have_cabin = df.loc[(pd.notna(df['Cabin']))]
have_cabin_survived = df.loc[((pd.notna(df['Cabin'])) & (df['Survived'] == 1))]
no_cabin = df.loc[(pd.isna(df['Cabin']))]
no_cabin_survived = df.loc[((pd.isna(df['Cabin'])) & (df['Survived'] == 1))]
# Визуализируем долю выживших
fig, ax = plt.subplots()
ax.bar(('Есть номер', 'Нет номера'), (len(have_cabin_survived)/len(have_cabin), len(no_cabin_survived)/len(no_cabin)))
ax.set_ylabel('Доля выживших')
ax.set_title('Выживаемость в зависимости от наличия каюты')
plt.show()
Действительно, видим, что наличие номера кабины у пассажира влияет на выживаемость. В [9] автор также рассматривает этот признак в разрезе по полу и классу каюты, и значимость признака подтверждается.
Родственники на борту
Различные авторы сходятся на том, что признаки "Родители+дети" и "Супруг+братья\сестры" стоит объединить в один признак "Семья", а также добавить признак "Пассажир путешествовал один" [9, 11, 12, 13, 14]. Чтобы составить собственное представление о влиянии этих признаков на выживаемость, построим диаграммы выживаемости для каждого из таких признаков, а затем взглянем на них через призму таблицы корреляции.
# Добавим признак family
#df.insert(13, 'Family', np.array(df.SibSp, int) + np.array(df.Parch, int))
sibsp_total = dict(Counter(df.SibSp))
parch_total = dict(Counter(df.Parch))
family_total = dict(Counter(df.Family))
sibsp_survived = dict(Counter(df.loc[(df['Survived'] == 1)].SibSp))
parch_survived = dict(Counter(df.loc[(df['Survived'] == 1)].Parch))
family_survived = dict(Counter(df.loc[(df['Survived'] == 1)].Family))
relatives = (family_total, sibsp_total, parch_total)
relatives_survived = (family_survived, sibsp_survived, parch_survived)
fig, axs = plt.subplots(1, 3, figsize=(15,5))
xlabs = ('Полное число родственников', 'Супруг+братья\сестры', 'Родители+дети')
for i in range(3):
probs = []
for rel, amount in relatives_survived[i].items():
probs.append(amount / relatives[i][rel])
axs[i].bar(relatives_survived[i].keys(), probs)
axs[i].set_ylabel('Доля выживших')
axs[i].set_xlabel(xlabs[i])
plt.show()
import seaborn as sns
# Добавим признак is_alone
is_alone = []
for fam in df.Family:
if fam == 0:
is_alone.append(1)
else:
is_alone.append(0)
df.insert(14, 'is_alone', is_alone)
# Correlation heatmap
sns.heatmap(df[['Survived', 'SibSp', 'Parch', 'Family', 'is_alone']].corr(), annot=True, vmin=-1, vmax=1, cmap=sns.diverging_palette(0, 500, as_cmap=True))
Как видно из гистограмм и таблицы выше, количество родственников хоть и влияет на выживаемость, но по отдельности коррелируют слабо. Будет разумно оставить только признак 'Family' и 'is_alone'.
Билеты и порт отправления
Стоимость билета, очевидно, будет коррелировать с классом каюты, а следовательно, влиять на вероятность выжить. Построим таблицу корреляции.
sns.heatmap(df[['Survived', 'Fare', 'Pclass']].corr(), annot=True, vmin=-1, vmax=1)
Стоимость билета оказывается хорошим признаком, влияющим на выживаемость. В дальнейшем также очистим этот признак от выбросов. А теперь построим диаграмму с распределением по стоимости билетов для каждого класса.
fare = dict.fromkeys((1,2,3), [])
for i, price in enumerate(df.Fare):
fare[df.Pclass[i]] = fare[df.Pclass[i]] + [price]
fig, ax = plt.subplots(figsize=(15,5))
ax.boxplot(fare.values(), labels=(1,2,3), vert=True)
ax.set_ylabel('Стоимость билета')
ax.set_xlabel('Класс каюты')
ax.set_ylim(-5, 250)
plt.show()
Из коробчатых диаграмм видно, что для каждого из классов есть своё характерное распределение по стоимости. Заменим билеты с нулевой стоимостью на медианную стоимость для соответствующего класса.
for i, fare in enumerate(df.Fare):
if np.isclose(fare, .0):
df.Fare[i] = np.median(fares[df.Pclass[i]])
«Здесь и ранее - идея заменять пропущенные значения какими-то усредненными величинами обосновывается стремлением оставить распределение данных примерно таким же, и не генерировать граничных/экстремальных значений, т.к. подобные выбросы могли бы увести алгоритмы прогнозирования в сторону от реального решения, давая не существующие ориентиры [14].»
Номер билета не несёт информации, способствующей предсказанию выживаемости, поэтому формировать какие-либо признаки на его основе не будем.
Пропущенные два значения порта отправления заменим на средние, тем более, что номер билета подсказывает такое же значение порта.
for i, emb in enumerate(df.Embarked):
if pd.isna(emb): df.Embarked[i] = 'S'
Построение модели
Формирование признаков (features)
Пол, класс каюты, возраст, наличие возраста, титул, наличие номера кабины, количество членов семьи, один ли пассажир, стоимость билета, порт отправления
В ходе анализа были отобраны качественные признаки для модели. Такими стали: Sex, Pclass, Age, Have_Age, Title(векторизованно), Have_Cabin, Family, Is_Alone, Fare, Embarked(векторизованно).
Следующий код конвертирует исходные данные в датасет признаков для тренировочного и тестового наборов.
import pandas as pd
import numpy as np
TITLES = ("Capt.","Col.","Major.","Sir.","Lady.","Rev.","Dr.","Don.","Jonkheer.","Countess.","Mrs.","Ms.","Mr.","Mme.","Mlle.","Miss.","Master.", "Dona.")
ARISTOCRATIC = ("Capt.", "Col.", "Don.", "Dr.",
"Jonkheer.", "Lady.", "Major.",
"Rev.", "Sir.", "Countess.", 'Dona')
MRS = ("Ms.")
MISS = ("Mlle.", 'Mme.')
for file in ('train', 'test'):
# Считываем файл
dataset = pd.read_csv(f'{file}.csv')
# Кодируем пол
dataset = dataset.replace({'female' : 1, 'male': 0})
# Восстанавливаем стоимость билетов
dataset.Fare.fillna(0, inplace = True)
fares = dict.fromkeys((1,2,3), [])
for i, price in enumerate(dataset.Fare):
fares[dataset.Pclass[i]] = fares[dataset.Pclass[i]] + [price]
for i, fare in enumerate(dataset.Fare):
if np.isclose(fare, .0):
dataset.loc[i, 'Fare'] = np.median(fares[dataset.Pclass[i]])
#Создадим список титулов для каждого пассажира
titled_names = []
for name in dataset.loc[:, 'Name']:
for title in TITLES:
if title in name.split(' '):
titled_names.append(title)
break
# Вставляем в датасет столбец Title
dataset.insert(1, 'Title', titled_names)
#Создадим словарь с парами "титул: список возрастов"
ages = dict.fromkeys(TITLES, [])
for i, title in enumerate(dataset.Title):
if not pd.isna(dataset.Age[i]):
ages[title] = ages[title] + [dataset.Age[i]]
# Создаем признак Семья
dataset['Family'] = dataset.Parch + dataset.SibSp
# Кодируем бинарные признаки
dataset['Is_Alone'] = dataset.Family == 0
dataset['Have_Cabin'] = pd.notna(dataset.Cabin)
dataset['Have_age'] = pd.notna(dataset.Age)
dataset = dataset.replace({True: 1, False: 0})
# Восстанавливаем пропущенные возраста
for i, age in enumerate(dataset.loc[:, 'Age']):
if pd.isna(age):
dataset.loc[i, 'Age'] = np.median(ages[dataset.Title[i]])
# Объединяем титулы
for i, title in enumerate(dataset.Title):
if title in ARISTOCRATIC:
dataset.loc[i, 'Title'] = 'Aristocratic.'
elif title in MISS:
dataset.loc[i, 'Title'] = 'Miss.'
elif title in MRS:
dataset.loc[i, 'Title'] = 'Mrs.'
# Восстанавливаем порт отправления
dataset.Embarked.fillna(dataset.Embarked.mode()[0], inplace = True)
# Кодируем порт и титулы
dataset = dataset.join(pd.get_dummies(dataset.Embarked, prefix='Emb'))
dataset = dataset.join(pd.get_dummies(dataset.Title, prefix='Title'))
# Чистим от лишних столбцов
dataset = dataset.drop(columns=['Ticket', 'Name', 'SibSp', 'Parch', 'Cabin', 'Title', 'Embarked'])
# Перемещаем столбец Survived в конец
try:
dataset.insert(dataset.shape[1] - 1, 'Survived', dataset.pop('Survived'))
except KeyError:
...
dataset.to_csv(f'clear_{file}.csv', index=False)
Построим таблицу корреляции всех отобранных признаков.
train_set = pd.read_csv('clear_train.csv')
fig, ax = plt.subplots(figsize=(15,15))
sns.heatmap(train_set.corr(), annot=True, vmin=-1, vmax=1, ax=ax)
Data preprocessing
Для того, чтобы изначально повысить эффективность обучения, нормализуем данные. Для этого объединим тренировочный и тестовый набор в один датафрейм и используем следующую формулу для преобразования ячеек в каждом столбце.
Нормализация реализована в файле data_functions.py
в виде следующей функции.
def normalize_data(train_vector: np.ndarray, test_vector: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
train_arr, test_arr = train_vector, test_vector
united_arr = np.concatenate((train_arr, test_arr))
mean = np.mean(united_arr, axis=0)
std_deviation = np.std(united_arr, axis=0)
train_X = (train_arr - mean) / std_deviation
test_X = (test_arr - mean) / std_deviation
return train_X, test_X
Для загрузки данных в модель создадим класс на основе структуры Dataset из pyTorch (см. файл data_functions.py
).
Структура нейронной сети
Структура выбранной сети выглядит следующим образом:
Входной слой: 16 нейронов (соответствует размерности входных данных).
Первый скрытый слой: 512 нейронов (с применением функции активации ReLU).
Батч-нормализация с 512 признаками.
Dropout-слой с вероятностью исключения нейронов 0.3 (30%).
Второй скрытый слой: 2048 нейронов (с применением функции активации ReLU).
Dropout-слой с вероятностью исключения нейронов 0.3 (30%).
Третий скрытый слой: 512 нейронов (с применением функции активации ReLU).
Dropout-слой с вероятностью исключения нейронов 0.3 (30%).
Выходной слой: 2 нейрона (с применением функции активации Softmax).
В коде эта нейросеть описана следующим образом (файл networks.py
):
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.layer_1 = nn.Linear(16, 512)
self.b_norm = nn.BatchNorm1d(512)
self.layer_2 = nn.Linear(512, 2048)
self.layer_3 = nn.Linear(2048, 512)
self.layer_4 = nn.Linear(512, 2)
self.dropout = nn.Dropout(0.3)
def forward(self, x):
x = self.layer_1(x)
x = F.relu(x)
x = self.dropout(x)
x = self.b_norm(x)
x = self.layer_2(x)
x = F.relu(x)
x = self.dropout(x)
x = self.layer_3(x)
x = F.relu(x)
x = self.dropout(x)
x = self.layer_4(x)
x = F.softmax(x)
return x
Визуализация структуры нейронной сети:
Нейронная сеть состоит из трех полносвязных слоев, между которыми расположены слои для регуляризации и нормализации. Техники батч-нормализации и Dropout использованы для предотвращения переобучения (overfitting). В выходном слое применяется функция активации Softmax для получения вероятностного распределения между двумя классами (выжил/погиб).
Обучение
Обучение нейросети проводилось в файле learning.py
. С выбранной структурой нейронной сети, наибольших успехов на тестовых данных удалось достичь со следующими параметрами обучения. Точность на валидационной выборке составила более 0.9.
Параметр |
Значение |
Описание |
---|---|---|
num_epochs |
1500 |
Количество эпох обучения |
batch_size |
64 |
Размер пакета данных (batch) |
learning_rate |
0.01 |
Скорость обучения (learning rate) |
weight_decay |
0.01 |
Коэффициент уменьшения весов (L2 регуляризация) |
validation_split |
0.1 |
Доля данных, выделенных для валидации |
optimizer |
Adam |
Тип оптимизатора |
annealing_factor |
0.6 |
Фактор уменьшения скорости обучения (LR annealing) |
loss_function |
CrossEntropyLoss |
Тип функции потерь |
В процессе обучения был использован LR Annealing, для которого задан фактор уменьшения скорости обучения 0.6 и количество эпох (patience) равное 100. Если значение функции потерь не уменьшалось в течение 100 эпох, скорость обучения уменьшалась на заданный фактор.
Далее представлены графики обучения сети с заданной структурой и параметрами. После этого обучения, на тестовых данных сеть показала точность 0.787.
Заключение
В работе был проведен анализ данных пассажиров парохода "Титаник", выявлено влияние различных факторов на вероятность человека выжить в катастрофе. Выбранные факторы обработаны и преобразованы для использования в нейронной сети. После построения и обучения модели нейросети на предоставленных данных результативность обученной модели на тестовых данных составила 0.78.
Ключевые факторы, влияющие на выживаемость пассажиров Титаника, были определены следующими: пол пассажира, возраст, класс каюты, наличие номера каюты, количество родственников на борту, путешествовал ли пассажир в одиночку. Использование нейросетевой модели позволило учесть сложные взаимодействия между этими факторами и предсказать вероятность выживания каждого пассажира с приемлемой точностью.
Для возможного улучшения модели, можно дополнительно исследовать исходные данные, попробовать включать и не включать различные из отобранных признаков, рассмотреть различные архитектуры нейронных сетей, варьировать гиперпараметры обучения, а также попробовать другие алгоритмы машинного обучения, такие как решающие деревья или алгоритмы на основе ансамбля, в целях увеличения точности предсказания выживаемости.
Список источников
Kaggle. Titanic: Machine Learning from Disaster [Электронный ресурс]. — Режим доступа: https://www.kaggle.com/competitions/titanic/. — Дата обращения: 02.04.2022.
Рахманов, А. В. Катастрофа Титаника: социально-классовая структура и шансы на спасение / А. В. Рахманов // Вестник Московского университета. Серия 18. Социология и политология. — 2016. — № 22. — С. 62-82.
Encyclopedia Titanica. Titanic Statistics [Электронный ресурс]. — Режим доступа: https://www.encyclopedia-titanica.org/titanic-statistics.html. — Дата обращения: 02.04.2022.
Gleicher, D. The Rescue of the Third Class on the Titanic: A Revisionist History / David Gleicher. — International Maritime Economic History Association, 2006. — (Research in Maritime History, No. 31). — ISBN 978-0-9738934-1-0.
Лорд, У. A Night to Remember / Уолтер Лорд. — Нью-Йорк: St. Martin's Griffin, 2005. — ISBN 978-0-8050-7764-3.
Barczewski, S. Titanic: A Night Rememb / Stephanie Barczewski. — Лондон: Continuum International Publishing Group, 2006. — ISBN 978-1-85285-500-0.
Губачек, М. С. Титаник / Милош Губачек. — Минск: Попурри, 2000. — 656 с. — ISBN 978-985-15-1679-3.
Википедия. English honorifics [Электронный ресурс]. — Режим доступа: https://en.wikipedia.org/wiki/English_honorifics. — Дата обращения: 02.04.2022.
Хабр. Титаник на Kaggle: вы не дочитаете этот пост до конца [Электронный ресурс]. — Режим доступа: https://habr.com/en/company/mlclass/blog/270973/. — Дата обращения: 02.04.2022.
Encyclopedia Titanica. The Cave List [Электронный ресурс]. — Режим доступа: https://www.encyclopedia-titanica.org/the-cave-list.html. — Дата обращения: 02.04.2022.
Хабр. Разбор задачи Титаник на Kaggle (Baseline) [Электронный ресурс]. — Режим доступа: https://habr.com/en/post/655955/. — Дата обращения: 02.04.2022.
ITnan. Kaggle и Titanic — еще одно решение задачи с помощью Python [Электронный ресурс]. — Режим доступа: https://itnan.ru/post.php?c=1&p=274171. — Дата обращения: 02.04.2022.
Neurohive. Разбор решения задачи «Титаник» на Kaggle для начинающих [Электронный ресурс]. — Режим доступа: https://neurohive.io/ru/osnovy-data-science/razbor-resheniya-zadachi-titanik-na-kaggle-dlya-nachinajushhih/. — Дата обращения: 02.04.2022.
Kaggle. Titanic Solution - a Beginner's Guide (Russian) [Электронный ресурс]. — Режим доступа: https://www.kaggle.com/code/adavydenko/titanic-solution-a-beginner-s-guide-russian/. — Дата обращения: 02.04.2022.
Комментарии (5)
koreychenko
02.04.2023 22:09+4Шо, опять Титаник?! Это же самое разбираемое и описываемое задание с Kaggle для новичков. Даже я ни разу не дата-сатанист его зачем-то делал.
А самая ржака, когда на том же Kaggle есть submission с prediction rate в районе 1.0. И когда я спросил своего разбирающегося в теме друга как такое возможно, он ответил типа: "Дык данные кто утонул на Титанике лежат в открытом доступе, что тут предсказывать!" :-)MentalSky
02.04.2023 22:09готовые ответы есть в нескольких соревнованиях-песочницах на каггле, что лично меня печалит - кто-то старается, работает, хочет скор побить чей-то реальный, а тут опа - в топе точность 100% )) а так была бы ачивка чисто для себя, что смог
Myclass
И где в Ваших расчётах Вы учли Леонардо Дикаприо? (шутка)
Спасибо, было интересно почитать. Хотя подумал для себя, что многие вами выбранные ключевые факторы, влияющие на выживаемость пассажиров на Титанике, как пол пассажира, возраст, класс каюты, итд. - в реальной ситуации не не играли и не играют роли. А на самом деле важно, где в момент катастрофы находился ты, и на сколько именно это место является эпицентром катастрофы или подвержённым сильным повреждениям. Может быть ещё физическая тренировка. Но такие вещи как наличие номера каюты или количество родственников на борту - изриняюсь, но это факторы, которые может быть в вашей логике и находят свои веса, но они не играют никакой роли. Ведь так можно и наличие и цвет пальто, количество нижнего белья или наличие и состояние зонтика или ещё чего учитывать.
Это мне похоже на одно из моих собеседований, что после 30 лет работы на разных фирмах на интервью на новую работу у меня не просто спросили, но именно очень сильно хотели видеть мой школьный атестат. Почему-то думаю, что в моих знаниях и опыте сегодня после 30 лет работы мои школьные оценки не отражаются ни в чём, даже если-бы они совсем плохие были.
И ещё. Как проделанная работа это есть гут. Но. Такие модели делают, когда есть своего рода повторение. А любая катастрофа - настолько вещь непредсказуемая, что даже и не знаю, как вы это хотите использовать. Я вспомнил катастрофу самолёта, где чудом выжила вроде женщина, которая была в туалете. И что - нахождение в туалете как-то можно спрогнозировать на вероятность выживания при будущих катастрофах?
Поэтому ваше высказывание
как-бы не имеет смысла. Выжить в какой катастрофе? Потому что именно такой-же технической катастрофы как Титаник больше не было и никогда не будет. Кроме как для само-обучения и работой с фраймворком - какие цели вы видите для именно этой модели?
pas-zhukov Автор
Вы, безусловно, правы. Данная модель, как и все исследование имеет отношение только к этой конкретной катастрофе (возможно, я недостаточно ясно пояснил это в тексте). Целью лично для себя я ставил - поучаствовать в простейшем соревновании kaggle и провести полноценную работу с данными. И именно эту работу я тут хотел продемонстрировать :)
Но замечу, такие выбранные факторы, как пол и класс, безусловно повлияли на шансы выжить на Титанике. Как я написал в статье, именно женщин и детей грузили в шлюпки первыми, а к первому классу всецело было больше внимания.
Если бы я хотел создать модель, которая предсказывает вероятность выжить для любой катастрофы, мне нужно было бы оперировать совсем другими данными, для множества катастроф, и даже на вскидку не сказать, что бы на что повлияло.
Myclass
Спасибо за ответ. В этом-то и загвоздка. Вы натренировали модель на каких-то данных, событие которых никогда не повторится. И ещё. Не совсем понял, как нейросеть связана с единожды собранными данными. Это больше на анализ данных похоже, чем на нейронную модель, которую тренировать надо, на всё новых и новых данных. Здесь-же простая статистика. Не так?
Понимание нейронных сетей - это хорошая вещь. Но и понимание, для чего они подходят, а для чего нет - тоже есть важно. Например натренировав какую-то модель на всех номерах, которые выпадала в какой-нибудь лотореи - вы не получаете никакой гарантии, что эта модель для будущих розыгрышей вам как-то поможет стать миллионером. Потому что госпожа "случайность" переваливает все другие факторы, какие-бы вы не использовали-бы в вашей модели.
Но в любом случае - успеха!