Перед тем, как мы начнем, я хочу поблагодарить бесконечное количество авторов ядра Medium и Kaggle, от которых исходит мой контент. Если вы просмотрели несколько проходов конкурса Titanic Kaggle, вы можете заметить, что многое из того, что у меня здесь, не выглядит оригинальным. Просто и понятно, многое не так.
Тем не менее, благодаря обширным исследованиям математики и статистики, лежащим в основе кода, а также анализу процесса научного мышления, лежащего в основе машинного обучения, от Эдуарда Харриса из SharpestMinds, я думаю, что у меня есть что предложить другим новичкам. как я.
Пожалуйста, используйте мой код и пройдите через это самостоятельно. Тогда, если у вас есть вопросы «почему», задавайте их ниже, и я отвечу! Также приветствуются критика и исправления!
Мы начнем с довольно стандартного импорта. Основными библиотеками, которые я использовал в целях этого эксперимента, были Pandas и Numpy для обработки данных, Pandas и Seaborn. для визуализации и scikit-learn для машинного обучения.
# regular expressions import re # math and data utilities import numpy as np import pandas as pd import scipy.stats as ss import itertools # data and statistics libraries import sklearn.preprocessing as pre from sklearn import model_selection from sklearn import metrics from sklearn.neighbors import KNeighborsClassifier from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier from sklearn.svm import SVC # visualization libraries import matplotlib.pyplot as plt import matplotlib as mpl import seaborn as sns # Set-up default visualization parameters mpl.rcParams[‘figure.figsize’] = [10,6] viz_dict = { ‘axes.titlesize’:18, ‘axes.labelsize’:16, } sns.set_context(“notebook”, rc=viz_dict) sns.set_style(“whitegrid”)
Начальная настройка
Мы можем загрузить данные из Kaggle в нашу папку данных с помощью командной строки:
kaggle competitions download -c titanic
unzip titanic.zip
После этого давайте поместим данные в несколько фреймов данных Pandas:
train_df = pd.read_csv('data/train.csv', index_col='PassengerId') test_df = pd.read_csv('data/test.csv', index_col='PassengerId')
Исследовательский анализ данных:
Следующим нашим шагом будет задать следующие вопросы и ответить на них:
- Нам не хватает данных?
- В какой форме принимают наши данные?
- Какую дополнительную информацию мы можем извлечь из того, что у нас уже есть?
- Какие отношения мы можем найти между нашими переменными, особенно между входными и выходными переменными?
- Как мы можем использовать ответы на первые два вопроса, чтобы повысить ценность наших данных и моделей, которые будут их использовать?
Вопрос 1: что нам не хватает?
Давайте посмотрим на количество записей в наших обучающих данных, а также на то, что эти переменные содержат существенные недостающие данные. Ниже мы видим, что обучающие данные содержат 891 выборку пассажиров с 11 переменными, описывающими каждого пассажира. Ниже, используя using.info()
, мы видим, что значительный объем данных для переменных Возраст, Посадка и Кабина отсутствует. Нам придется иметь дело с этими недостающими данными, либо найдя разумный способ заполнить пробелы, либо, возможно, полностью отбросив функции.
# Look for missing values train_df.info() # Output: <class 'pandas.core.frame.DataFrame'> Int64Index: 891 entries, 1 to 891 Data columns (total 11 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Survived 891 non-null int64 1 Pclass 891 non-null int64 2 Name 891 non-null object 3 Sex 891 non-null object 4 Age 714 non-null float64 5 SibSp 891 non-null int64 6 Parch 891 non-null int64 7 Ticket 891 non-null object 8 Fare 891 non-null float64 9 Cabin 204 non-null object 10 Embarked 889 non-null object dtypes: float64(2), int64(4), object(5) memory usage: 83.5+ KB
Вопрос 2: Какова форма наших данных?
Взглянув на нашу .info()
распечатку, а также на несколько первых записей нашего фрейма данных ниже, мы видим, что наши данные поступают в основном в форме категориальных данных, за исключением возраста и Стоимость. Эти категории описываются строками Python, поэтому тип данных выше указан как «объект». Вот как Pandas работает с неопознанными типами данных. Позже мы скажем Pandas, что эти переменные являются строками.
# Look at the first few entries. train_df.head()
Вопрос 3: Какую дополнительную информацию мы можем извлечь из того, что у нас уже есть?
Название пассажира
Быстрый взгляд на переменную Имя показывает, что у каждого имени есть заголовок. Заголовок полезен для того, чтобы сообщить нам такие вещи, как социальный статус, карьера в семейном статусе и даже ранг в конкретной карьере. Поэтому может быть полезно иметь эту информацию под рукой. Давайте разберем заголовки из переменной Name и добавим ее как отдельную функцию:
# Question about what's going on here? Google "Regular Expressions" train_df['Title'] = train_df['Name'].str.extract(r'([A-Za-z]+)\.') train_df.Title.value_counts()
Выход:
Mr 517 Miss 182 Mrs 125 Master 40 Dr 7 Rev 6 Col 2 Mlle 2 Major 2 Don 1 Capt 1 Sir 1 Jonkheer 1 Countess 1 Mme 1 Ms 1 Lady 1 Name: Title, dtype: int64
Затем мы можем заметить, что многие из этих названий являются синонимами. Например, Mme - это французский эквивалент «миссис», а Mlle - эквивалент «мисс». Другие титулы подразумевают разные уровни благородства, такие как «Сэр», «Графиня» и «Дон». Некоторые титулы подразумевают профессию. Давайте уменьшим наши названия до их общих знаменателей:
# This dict will map redundant titles to their equivilent. title_dict = { 'Mrs': 'Mrs', 'Lady': 'Lady', 'Countess':'Lady', 'Jonkheer':'Lord', 'Col': 'Officer', 'Rev': 'Rev', 'Miss': 'Miss', 'Mlle': 'Miss', 'Mme': 'Mrs', 'Ms': 'Miss', 'Dona': 'Lady', 'Mr': 'Mr', 'Dr': 'Dr', 'Major': 'Officer', 'Capt': 'Officer', 'Sir': 'Lord', 'Don': 'Lord', 'Master': 'Master' } # Create new feature/variable in DataFrame. train_df.Title = train_df.Title.map(title_dict)
Теперь, чтобы лучше познакомиться с нашими данными, давайте посмотрим на распределение титулов пассажиров:
Похоже, что путешествие Титаника не обязательно было делом пары или семьи. Большинство пассажиров ехали одни, и, возможно, это ценная информация. Давайте добавим категорию Один.
# Use of Python lambda expressions is very useful for parsing data: train_df['Alone'] = train_df.FamilySize.apply(lambda x: 1 if x==1 else 0) # Display distribution of 'Alone' vs 'Not Alone' plt.figure(figsize=(8,5)) sns.countplot(train_df.Alone)
Фамилия
Фамилия - это групповая принадлежность. Хотя мы знаем, что многие пассажиры путешествовали в одиночку, на борту Титаника все еще находилось значительное количество семей. Возможно, выживание среди определенных семей было более распространенным, чем среди других. Это все предположения, но, возможно, стоит посмотреть.
# Once again, regular expressions come in handy. train_df['LName'] = train_df.Name.str.extract(r'([A-Za-z]+),')
Имя Длина
У этого есть очень простое объяснение: просматривая ноутбуки на Kaggle, я увидел, что один конкурент обнаружил, что длина имени человека увеличивает производительность модели. Так почему бы не попробовать?
# Checkout DataFrame.apply() and Series.apply() in the docs. train_df['NameLength'] = train_df.Name.apply(len)
Вопрос 4: Какие статистические взаимосвязи содержат наши данные?
После объяснения некоторых важных статистических терминов в этом разделе будет очень много кода. Если код окажется у вас в голове, я рекомендую вам сначала прочитать документацию, а затем задать вопросы ниже!
Теперь у нас есть более надежный набор данных, который включает (возможно) новые ценные сведения о жизни наших пассажиров. Но насколько на самом деле полезны эти данные? Один из способов выяснить это - посмотреть на статистические отношения между нашими переменными, особенно между каждой входной переменной и нашей единственной выходной переменной Выжил.
Корреляция - это обычный инструмент, который мы использовали бы для определения таких отношений. Однако важно отметить, что в нашем наборе данных есть в основном категориальные данные, и это немного мешает нам.
Во-первых, наши категориальные данные необходимо закодировать в числовой формат, прежде чем мы сможем выполнять какие-либо вычисления.
Далее нам необходимо рассмотреть изучаемые нами типы категорий:
- Порядковые переменные подразумевают базовый ранг или порядок. Примером могут служить классификации легкой, средней и тяжелой степени. Распространенный метод вычисления корреляции между порядковыми переменными называется Тау Кендалла ( 𝜏 ).
- Номинальные переменные не имеют такого ранга или порядка. Примеры могут быть мужчиной или женщиной, кошкой или собакой. В этом случае мы будем использовать Cramer’s V для определения ассоциации.
# nominal variables (use Cramer's V) nom_vars = ['Survived', 'Title', 'Embarked', 'Sex', 'Alone', 'LName'] # ordinal variables (nominal-ordinal, use Rank Biserial or Kendall's Tau) ord_vars = ['Survived', 'Pclass', 'FamilySize', 'Parch', 'SibSp', 'NameLength'] # continuous variables (use Pearson's r) cont_vars = ['Survived', 'Fare', 'Age']
В ячейке выше мы разделяем наши переменные по их типам данных. Причина этого в том, что при рассмотрении основных ассоциаций между переменными не существует универсального метода. Наиболее распространенным математическим методом вычисления корреляции является коэффициент Пирсона, который обычно следует использовать только для непрерывных переменных. В нашем случае подавляющее большинство переменных на самом деле являются дискретными / категориальными.
Чтобы выполнить вычисления, мы должны преобразовать любые нечисловые данные в числа. Вы не можете вычислить слова, поэтому приступим:
# convert all string 'object' types to numeric categories for i in train_df.columns: if train_df[i].dtype == 'object': train_df[i], _ = pd.factorize(train_df[i])
Далее нам нужен алгоритм для вычисления и отображения результатов измерений V-ассоциации Крамера:
# A method that creates a correlation matrix in the form of a Pandas DataFrame using Cramer's V. def cramers_v_matrix(dataframe, variables): df = pd.DataFrame(index=dataframe[variables].columns, columns=dataframe[variables].columns, dtype="float64") for v1, v2 in itertools.combinations(variables, 2): # generate contingency table: table = pd.crosstab(dataframe[v1], dataframe[v2]) n = len(dataframe.index) r, k = table.shape # calculate chi squared and phi chi2 = ss.chi2_contingency(table)[0] phi2 = chi2/n # bias corrections: r = r - ((r - 1)**2)/(n - 1) k = k - ((k - 1)**2)/(n - 1) phi2 = max(0, phi2 - (k - 1)*(r - 1)/(n - 1)) # fill correlation matrix df.loc[v1, v2] = np.sqrt(phi2/min(k - 1, r - 1)) df.loc[v2, v1] = np.sqrt(phi2/min(k - 1, r - 1)) np.fill_diagonal(df.values, np.ones(len(df))) return df
Теперь, заставляя нашу новую функцию работать, давайте посмотрим, как складываются наши данные:
fig, axes = plt.subplots(1, 3, figsize=(20,6)) # nominal variable correlation ax1 = sns.heatmap(cramers_v_matrix(train_df, nom_vars), annot=True, ax=axes[0], vmin=0) # ordinal variable correlation: ax2 = sns.heatmap(train_df[ord_vars].corr(method='kendall'), annot=True, ax=axes[1], vmin=-1) # Pearson's correlation: ax3 = sns.heatmap(train_df[cont_vars].corr(), annot=True, ax=axes[2], vmin=-1) ax1.set_title("Cramer's V Correlation") ax2.set_title("Kendall's Tau Correlation") ax3.set_title("Pearson's R Correlation")
Приведенные выше тепловые карты показывают нашу силу связи между каждой переменной. Несмотря на то, что нет жесткого стандарта для «сильно ассоциированных» или «слабо связанных», мы будем использовать пороговое значение | 0,1 | между нашими независимыми переменными и выживанием. Скорее всего, мы удалим объекты, ассоциация которых ниже | 0,1 |. Это совершенно произвольное предположение, и я могу вернуться, чтобы поднять или опустить планку позже (на самом деле, я решил оставить Age после того, как заметил улучшение производительности, когда я это сделал).
На данный момент критериям для исключения соответствует функция SibSp. Кроме того, я предпочитаю опустить Имя, Билет и Каюта, в основном потому, что они не добавляют особого смысла.
Следует отметить, что корреляция между независимыми (предикторами) переменных может означать избыточную информацию. Это может вызвать падение производительности некоторых алгоритмов. Некоторые могут отказаться от сильно коррелированных предикторов. У меня не было времени на это, но вы можете попробовать это дома!
todrop = ['SibSp', 'Ticket', 'Cabin', 'Name'] train_df = train_df.drop(todrop, axis=1)
Давайте посмотрим на наш преобразованный фрейм данных, изобилующий новыми функциями, категориями, преобразованными в числовые данные, и удаленными старыми функциями:
Настройка для машинного обучения:
На этом этапе мы начнем форматировать наши данные для ввода в алгоритм машинного обучения. Затем мы будем использовать эти отформатированные данные, чтобы получить представление о том, что несколько разных моделей могут сделать для нас, и выбрать лучшую. Этот этап разбит на следующие части:
- Разделение на тренировку / тест
- Нормализовать данные каждой группы
- Вменять отсутствующие значения
Пойдем.
Тренировка / тестовый сплит
Мы разделим наши данные один раз на наборы для обучения и тестирования. В рамках обучающего набора мы будем использовать стратифицированную k-кратную перекрестную проверку, чтобы найти среднюю производительность наших моделей.
Набор тестов не будет затронут до тех пор, пока мы полностью не настроим каждую из наших моделей-кандидатов, используя данные обучения и перекрестную проверку в k-кратном размере. После завершения обучения и настройки мы сравним результаты каждой модели на проведенном тестовом наборе. Тот, который показывает лучшие результаты, будет использован для конкурса.
# Split dependant and independant variables X = train_df.drop(['Survived'], axis = 1) Y = train_df.loc[:, 'Survived'] # Split data into training and validation sets x_train, x_test, y_train, y_test = model_selection.train_test_split(X, Y, test_size=0.2, random_state=333)
Нормализация данных
Некоторые модели машинного обучения требуют, чтобы все наши предикторы были в одном масштабе, а другие - нет. В частности, такие модели, как логистическая регрессия и SVM, вероятно, выиграют от масштабирования, в то время как деревья решений просто игнорируют масштабирование. Поскольку мы собираемся рассмотреть смешанный набор алгоритмов, я собираюсь продолжить и масштабировать наши данные.
# We normalize the training and testing data separately so as to avoid data leaks. Ask at the end! x_train = pd.DataFrame(pre.scale(x_train), columns=x_train.columns, index=x_train.index) x_test = pd.DataFrame(pre.scale(x_test), columns=x_test.columns, index=x_test.index)
Вменение отсутствующих данных
Как вы помните, в наших данных отсутствовало значительное количество значений возраста. Давайте заполним это средним возрастом:
# Again, applying changes to the now separate datasets helps us avoid data leaks. x_train.loc[x_train.Age.isnull(), 'Age'] = x_train.loc[:, 'Age'] .median() x_test.loc[x_test.Age.isnull(), 'Age'] = x_test.loc[:, 'Age'] .median()
Давайте удостоверимся, что наши недостающие данные заполнены:
x_train.info() # Output: <class 'pandas.core.frame.DataFrame'> Int64Index: 712 entries, 466 to 781 Data columns (total 11 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Pclass 712 non-null float64 1 Sex 712 non-null float64 2 Age 712 non-null float64 3 Parch 712 non-null float64 4 Fare 712 non-null float64 5 Embarked 712 non-null float64 6 Title 712 non-null float64 7 FamilySize 712 non-null float64 8 Alone 712 non-null float64 9 LName 712 non-null float64 10 NameLength 712 non-null float64 dtypes: float64(11)
Теперь мы видим, что каждая переменная, которую мы решили сохранить, имеет 712 действительных записей данных.
Выбор модели
Теперь, когда мы подготовили наши данные, мы хотим рассмотреть различные доступные нам варианты решения проблем классификации. Вот некоторые из распространенных:
- K-Ближайшие соседи
- Машины опорных векторов
- Деревья решений
- Логистическая регрессия
Мы обучим и настроим каждую из этих моделей на наших обучающих данных с помощью k-кратной перекрестной проверки. По завершении мы сравним характеристики настроенных моделей на проведенном тестовом наборе.
Обучение и сравнение базовых моделей:
Во-первых, мы хотим получить представление о производительности модели перед настройкой. Мы напишем две функции, которые помогут нам описать наши результаты. Первый будет оценивать модель несколько раз по случайным разделам данных и возвращать среднюю производительность в виде словаря. Второй просто красиво распечатает наш словарь.
# A function that evaluates each model and gives us the results: def kfold_evaluate(model, folds=5): eval_dict = {} accuracy = 0 f1 = 0 AUC = 0 skf = model_selection.StratifiedKFold(n_splits=folds) # perform k splits on the training data. for train_idx, test_idx in skf.split(x_train, y_train): xk_train, xk_test = x_train.iloc[train_idx], x_train.iloc[test_idx] yk_train, yk_test = y_train.iloc[train_idx], y_train.iloc[test_idx] # Test performance on this fold: model.fit(xk_train, yk_train) y_pred = model.predict(xk_test) report = metrics.classification_report(yk_test, y_pred, output_dict=True) # Gather performance metrics for output prob_array = model.predict_proba(xk_test) fpr, tpr, huh = metrics.roc_curve(yk_test, model.predict_proba(xk_test)[:,1]) auc = metrics.auc(fpr, tpr) accuracy += report['accuracy'] f1 += report['macro avg']['f1-score'] AUC += auc # Average performance metrics over the k folds measures = np.array([accuracy, f1, AUC]) measures = measures/folds # Add metric averages to dictionary and return. eval_dict['Accuracy'] = measures[0] eval_dict['F1 Score'] = measures[1] eval_dict['AUC'] = measures[2] eval_dict['Model'] = model return eval_dict # a function to pretty print our dictionary of dictionaries: def pprint(web, level): for k,v in web.items(): if isinstance(v, dict): print('\t'*level, f'{k}: ') level += 1 pprint(v, level) level -= 1 else: print('\t'*level, k, ": ", v)
Используем нашу оценочную функцию kfold:
# Perform evaluation on each model: evals = {} evals['KNN'] = kfold_evaluate(KNeighborsClassifier()) evals['Logistic Regression'] = kfold_evaluate(LogisticRegression(max_iter=1000)) evals['Random Forest'] = kfold_evaluate(RandomForestClassifier()) evals['SVC'] = kfold_evaluate(SVC(probability=True)) # Plot results for visual comparison: result_df = pd.DataFrame(evals) result_df .drop('Model', axis=0) .plot(kind='bar', ylim=(0.7, 0.9)) .set_title("Base Model Performance") plt.xticks(rotation=0) plt.show()
Резюме базовой модели
Похоже, что у нас есть явный победитель в нашем классификаторе случайного леса.
Настройка гиперпараметров:
Давайте настроим гиперпараметры нашего действующего чемпиона в надежде добиться чуть большей производительности. Мы будем использовать RandomizedSearchCV
из scikit-learn, который имеет некоторые преимущества в скорости по сравнению с исчерпывающим GridSearchCV
. Наш первый шаг - создать нашу сетку параметров, по которой мы будем случайным образом искать лучшие настройки:
# Number of trees in random forest n_estimators = [int(x) for x in np.linspace(start = 200, stop = 2000, num = 10)] # Number of features to consider at every split max_features = ['auto', 'sqrt'] # Maximum number of levels in tree max_depth = [int(x) for x in np.linspace(10, 110, num = 11)] max_depth.append(None) # Minimum number of samples required to split a node min_samples_split = [2, 5, 10] # Minimum number of samples required at each leaf node min_samples_leaf = [1, 2, 4] # Method of selecting samples for training each tree bootstrap = [True, False] # Create the random grid from above parameters random_grid = {'n_estimators': n_estimators, 'max_features': max_features, 'max_depth': max_depth, 'min_samples_split': min_samples_split, 'min_samples_leaf': min_samples_leaf, 'bootstrap': bootstrap} pprint(random_grid, 0) #Output: n_estimators : [200, 400, 600, 800, 1000, 1200, 1400, 1600, 1800, 2000] max_features : ['auto', 'sqrt'] max_depth : [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, None] min_samples_split : [2, 5, 10] min_samples_leaf : [1, 2, 4] bootstrap : [True, False]
Затем мы хотим создать наш объект RandomizedSearchCV
, который будет использовать сетку, которую мы только что создали. Он случайным образом выберет 10 комбинаций параметров, проверит их более чем в 3 раза и вернет набор параметров, которые показали наилучшие результаты на наших обучающих данных.
# create RandomizedSearchCV object searcher = model_selection.RandomizedSearchCV( estimator = RandomForestClassifier(), param_distributions = random_grid, n_iter = 10, # Number of parameter settings to sample cv = 3, # Number of folds for k-fold validation n_jobs = -1, # Use all processors to compute in parallel random_state=0 # Fore reproducible results ) # Look for the best parameters search = searcher.fit(x_train, y_train) params = search.best_params_ params #Output: {'n_estimators': 1600, 'min_samples_split': 10, 'min_samples_leaf': 4, 'max_features': 'auto', 'max_depth': 30, 'bootstrap': False}
После настройки параметров мы можем проверить, действительно ли параметры, предоставленные поиском, улучшают базовую модель или нет. Давайте сравним производительность двух моделей до и после настройки.
tuning_eval = {} tuned_rf = RandomForestClassifier(**params) basic_rf = RandomForestClassifier() tuning_eval['Tuned'] = kfold_evaluate(tuned_rf) tuning_eval['Basic'] = kfold_evaluate(basic_rf) result_df = pd.DataFrame(tuning_eval) result_df.drop('Model', axis=0).plot(kind='bar', ylim=(0.7, 0.9)).set_title("Tuning Performance") plt.xticks(rotation=0) plt.show() result_df
Заключительные шаги:
Теперь, когда мы выбрали и настроили классификатор случайного леса, мы хотим протестировать его на данных, которых он никогда раньше не видел. Это расскажет нам, как мы можем ожидать, что модель будет работать в будущем на новых данных. Пришло время использовать этот тестовый набор.
Затем мы объединим тестовые и обучающие данные и повторно подгоним нашу модель к объединенному набору данных, надеясь, что это даст ей наибольшие шансы на успех на немаркированных данных из конкурса.
Наконец, мы сделаем наши прогнозы на немаркированных данных для подачи на конкурс.
Заключительный тест на хранимых данных
# Get tuned model predictions on held out data y_pred = tuned_rf.predict(x_test) # Compare predictions to actual answers and show performance results = metrics.classification_report(y_test, y_pred, labels = [0, 1], target_names = ['Died', 'Survived'], output_dict = True) pprint(results, 0)
А вот как работала наша модель:
Died: precision : 0.7815126050420168 recall : 0.8532110091743119 f1-score : 0.8157894736842106 support : 109 Survived: precision : 0.7333333333333333 recall : 0.6285714285714286 f1-score : 0.6769230769230768 support : 70 accuracy : 0.7653631284916201 macro avg: precision : 0.757422969187675 recall : 0.7408912188728702 f1-score : 0.7463562753036437 support : 179 weighted avg: precision : 0.7626715490665541 recall : 0.7653631284916201 f1-score : 0.761484178861421 support : 179
Похоже, мы столкнулись с переоборудованием. Эффективность нашей модели на тестовых данных примерно на 7–9% ниже по всем направлениям, но мы должны ожидать, что наша модель работает примерно так же хорошо на реальных данных, которых она никогда раньше не видела.
Объедините наборы данных для обучения и тестирования для окончательной подгонки модели
Теперь, когда мы убедились, что наша настроенная модель работает с точностью около 76% и имеет показатель f1 0,74 на новых данных, мы можем приступить к обучению нашей модели на всем помеченном обучающем наборе. Больше (хороших) данных почти всегда лучше для алгоритма.
X = pd.concat([x_train, x_test], axis=0).sort_index() Y = pd.concat([y_train, y_test], axis=0).sort_index() tuned_rf.fit(X, Y)
Форматирование и стандартизация немаркированных данных
Теперь, когда наша модель полностью адаптирована к данным обучения, пора подготовиться к прогнозам, которые мы представим на конкурс.
Нам необходимо преобразовать немаркированные данные о соревнованиях таким же образом, как при форматировании данных для обучения. Это включает в себя кодирование категориальных переменных, удаление тех же функций и нормализацию. Идея здесь в последовательности. То, что мы сделали с данными, на которых мы обучили модель, нам нужно сделать с данными, которые мы будем использовать, чтобы сделать наши окончательные прогнозы.
# Feature Engineering: test_df['Title'] = test_df.Name.str.extract(r'([A-Za-z]+)\.') test_df['LName'] = test_df.Name.str.extract(r'([A-Za-z]+),') test_df['NameLength'] = test_df.Name.apply(len) test_df['FamilySize'] = 1 + test_df.SibSp + test_df.Parch test_df['Alone'] = test_df.FamilySize.apply(lambda x: 1 if x==1 else 0) test_df.Title = test_df.Title.map(title_dict) # Feature Selection test_df = test_df.drop(todrop, axis=1) # Imputation of missing age and fare data test_df.loc[test_df.Age.isna(), 'Age'] = test_df.Age.median() test_df.loc[test_df.Fare.isna(), 'Fare'] = test_df.Fare.median() # encode categorical data for i in test_df.columns: if test_df[i].dtype == 'object': test_df[i], _ = pd.factorize(test_df[i]) # center and scale data test_df = pd.DataFrame(pre.scale(test_df), columns=test_df.columns, index=test_df.index) # ensure columns of unlabeled data are in same order as training data. test_df = test_df[x_test.columns] test_df
Сделайте окончательные прогнозы и проверьте здравый смысл:
Около 32 процентов пассажиров «Титаника» жили. Мы сделаем последнюю проверку здравого смысла, чтобы увидеть, предсказывает ли наш алгоритм примерно такое же распределение выживших. Поскольку переменная Survived со значением 1 подразумевает выживаемость, мы можем просто сложить все случаи выживания и разделить на общее количество пассажиров, чтобы получить приблизительное представление о нашем прогнозируемом распределении.
Имейте в виду, что организаторы соревнований могли пойти на хитрость и дать нам неравномерное распределение для обучения и тестирования. В этом случае это может не сработать, но я предполагаю, что это не так.
# Make final predictions final = tuned_rf.predict(test_df) # Check the probability of survival according to our predictions. It should be roughly 32% (we get 36.6% which is a bit optimistic) final.sum()/len(final) # Get our predictions in the competition rules format: submission = pd.DataFrame({'PassengerId':test_df.index, 'Survived':final}) # Output our submission data to a .csv file: submission.to_csv('submission2.csv', index=False)
Резюме
Моя цель заключалась не в том, чтобы выиграть соревнование, а в том, чтобы научиться мыслить. В конце концов, если вы похожи на меня, вы стремитесь стать специалистом по данным ученым. Следовательно, эксперименты с машинным обучением должны быть строгими и повторяемыми, но, что не менее важно, процесс должен однозначно определяться задаваемыми вопросами и имеющимися данными для ответа на эти вопросы.
Однако, если вам интересно, насколько хорошо работает эта настройка, она достигла точности 77%. Это далеко не идеально!
Опять же, если у вас есть отзывы, Я хотел бы услышать ваши вопросы, комментарии и критические отзывы о моем процессе. Написание этой статьи - часть моего собственного учебного процесса, и я надеюсь, что вы присоединитесь к нему.
Спасибо!