Предисловие автора
Данную статью я решил написать в качестве своей первой основательной работы по анализу данных и созданию предиктивной модели на базе нейронных сетей. Все упоминающиеся в тексте файлы можно найти в репозитории этой работы на 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.