Статьи

Python: создание scikit-learn и Pandas Play Nice

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

Нам нужно было написать алгоритм классификации, чтобы определить, выжил ли человек на борту «Титаника», и, к счастью, у scikit-learn есть обширная документация по каждому из этих алгоритмов .

К сожалению, почти во всех этих примерах используются сложные структуры данных, и мы загрузили данные, используя панд, и не хотели переключаться обратно!

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

Например, если бы мы подключили ExtraTreesClassifier, который определял выживаемость на основе атрибутов ‘Fare’ и ‘Pclass’, мы могли бы написать следующий код:

import pandas as pd
from sklearn.ensemble import ExtraTreesClassifier
from sklearn.cross_validation import cross_val_score
 
train_df = pd.read_csv("train.csv")
 
et = ExtraTreesClassifier(n_estimators=100, max_depth=None, min_samples_split=1, random_state=0)
 
columns = ["Fare", "Pclass"]
 
labels = train_df["Survived"].values
features = train_df[list(columns)].values
 
et_score = cross_val_score(et, features, labels, n_jobs=-1).mean()
 
print("{0} -> ET: {1})".format(columns, et_score))

Начнем с чтения в файле CSV, который выглядит следующим образом:

$ head -n5 train.csv 
PassengerId,Survived,Pclass,Name,Sex,Age,SibSp,Parch,Ticket,Fare,Cabin,Embarked
1,0,3,"Braund, Mr. Owen Harris",male,22,1,0,A/5 21171,7.25,,S
2,1,1,"Cumings, Mrs. John Bradley (Florence Briggs Thayer)",female,38,1,0,PC 17599,71.2833,C85,C
3,1,3,"Heikkinen, Miss. Laina",female,26,0,0,STON/O2. 3101282,7.925,,S
4,1,1,"Futrelle, Mrs. Jacques Heath (Lily May Peel)",female,35,1,0,113803,53.1,C123,S

Затем мы создаем наш классификатор, который « соответствует ряду рандомизированных деревьев решений (также называемых дополнительными деревьями) на различных подвыборках набора данных и используем усреднение для повышения точности прогнозирования и контроля соответствия. «То есть лучшая версия случайного леса .

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

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

Если мы запустим этот код, мы получим примерно следующий вывод:

$ python et.py
 
['Fare', 'Pclass'] -> ET: 0.687991021324)

Это на самом деле хуже, чем мы могли бы сказать, что женщины выжили, а мужчины нет.

Мы можем ввести «Секс» в классификатор, добавив его в список столбцов:

columns = ["Fare", "Pclass", "Sex"]

Если мы повторно запустим код, мы получим следующую ошибку:

$ python et.py
 
An unexpected error occurred while tokenizing input
The following traceback may be corrupted or invalid
The error message is: ('EOF in multi-line statement', (514, 0))
...
Traceback (most recent call last):
  File "et.py", line 14, in <module>
    et_score = cross_val_score(et, features, labels, n_jobs=-1).mean()
  File "/Library/Python/2.7/site-packages/scikit_learn-0.14.1-py2.7-macosx-10.8-intel.egg/sklearn/cross_validation.py", line 1152, in cross_val_score
    for train, test in cv)
  File "/Library/Python/2.7/site-packages/scikit_learn-0.14.1-py2.7-macosx-10.8-intel.egg/sklearn/externals/joblib/parallel.py", line 519, in __call__
    self.retrieve()
  File "/Library/Python/2.7/site-packages/scikit_learn-0.14.1-py2.7-macosx-10.8-intel.egg/sklearn/externals/joblib/parallel.py", line 450, in retrieve
    raise exception_type(report)
sklearn.externals.joblib.my_exceptions.JoblibValueError/Library/Python/2.7/site-packages/scikit_learn-0.14.1-py2.7-macosx-10.8-intel.egg/sklearn/externals/joblib/my_exceptions.py:26: DeprecationWarning: BaseException.message has been deprecated as of Python 2.6
  self.message,
: JoblibValueError
___________________________________________________________________________
Multiprocessing exception:
...
ValueError: could not convert string to float: male
___________________________________________________________________________

Это немного многословный способ сказать нам, что мы не можем передавать нечисловые признаки в классификатор — в этом случае «Секс» имеет значения «женский» и «мужской». Нам нужно написать функцию для замены этих значений числовыми эквивалентами.

train_df["Sex"] = train_df["Sex"].apply(lambda sex: 0 if sex == "male" else 1)

Теперь, если мы повторно запустим классификатор, мы получим немного более точный прогноз:

$ python et.py 
['Fare', 'Pclass', 'Sex'] -> ET: 0.813692480359)

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

test_df = pd.read_csv("test.csv")
 
et.fit(features, labels)
et.predict(test_df[columns].values)

Теперь, если мы запустим это:

$ python et.py 
['Fare', 'Pclass', 'Sex'] -> ET: 0.813692480359)
Traceback (most recent call last):
  File "et.py", line 22, in <module>
    et.predict(test_df[columns].values)
  File "/Library/Python/2.7/site-packages/scikit_learn-0.14.1-py2.7-macosx-10.8-intel.egg/sklearn/ensemble/forest.py", line 444, in predict
    proba = self.predict_proba(X)
  File "/Library/Python/2.7/site-packages/scikit_learn-0.14.1-py2.7-macosx-10.8-intel.egg/sklearn/ensemble/forest.py", line 479, in predict_proba
    X = array2d(X, dtype=DTYPE)
  File "/Library/Python/2.7/site-packages/scikit_learn-0.14.1-py2.7-macosx-10.8-intel.egg/sklearn/utils/validation.py", line 91, in array2d
    X_2d = np.asarray(np.atleast_2d(X), dtype=dtype, order=order)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/numpy/core/numeric.py", line 235, in asarray
    return array(a, dtype, copy=False, order=order)
ValueError: could not convert string to float: male

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

def replace_non_numeric(df):
	df["Sex"] = df["Sex"].apply(lambda sex: 0 if sex == "male" else 1)
	return df

Теперь мы вызовем эту функцию с нашими фреймами данных обучения и тестирования:

train_df = replace_non_numeric(pd.read_csv("train.csv"))
...
test_df = replace_non_numeric(pd.read_csv("test.csv"))

Если мы запустим программу снова:

$ python et.py 
['Fare', 'Pclass', 'Sex'] -> ET: 0.813692480359)
Traceback (most recent call last):
  File "et.py", line 26, in <module>
    et.predict(test_df[columns].values)
  File "/Library/Python/2.7/site-packages/scikit_learn-0.14.1-py2.7-macosx-10.8-intel.egg/sklearn/ensemble/forest.py", line 444, in predict
    proba = self.predict_proba(X)
  File "/Library/Python/2.7/site-packages/scikit_learn-0.14.1-py2.7-macosx-10.8-intel.egg/sklearn/ensemble/forest.py", line 479, in predict_proba
    X = array2d(X, dtype=DTYPE)
  File "/Library/Python/2.7/site-packages/scikit_learn-0.14.1-py2.7-macosx-10.8-intel.egg/sklearn/utils/validation.py", line 93, in array2d
    _assert_all_finite(X_2d)
  File "/Library/Python/2.7/site-packages/scikit_learn-0.14.1-py2.7-macosx-10.8-intel.egg/sklearn/utils/validation.py", line 27, in _assert_all_finite
    raise ValueError("Array contains NaN or infinity.")
ValueError: Array contains NaN or infinity.

В тестовом наборе отсутствуют значения, поэтому мы заменим их на средние значения из нашего обучающего набора, используя Imputer :

from sklearn.preprocessing import Imputer
 
imp = Imputer(missing_values='NaN', strategy='mean', axis=0)
imp.fit(features)
 
test_df = replace_non_numeric(pd.read_csv("test.csv"))
 
et.fit(features, labels)
print et.predict(imp.transform(test_df[columns].values))

Если мы запустим его, он завершится успешно:

$ python et.py 
['Fare', 'Pclass', 'Sex'] -> ET: 0.813692480359)
[0 1 0 0 1 0 0 1 1 0 0 0 1 0 1 1 0 0 1 1 0 0 1 0 1 0 1 0 1 0 0 0 1 0 1 0 0
 0 0 1 0 0 0 1 1 0 0 0 1 1 0 0 1 1 0 0 0 0 0 1 0 0 0 1 0 1 1 0 0 1 1 0 1 0
 1 0 0 1 0 1 1 0 0 0 0 0 1 0 1 0 1 0 1 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0
 1 1 1 1 0 0 1 1 1 1 0 1 0 0 1 0 1 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 1 0 0 1 0 0 1 1 1 1 0 0 1 0 0 1 0 0 0 0 0 0 1 1 1 1 1 0 0 1 0 1
 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 1 0 0 1 0 1 0 0 0 0 1 0 0 1 0 1 0 1 0
 1 0 1 0 0 1 0 0 0 1 0 0 0 0 1 0 1 1 1 1 1 0 0 0 1 0 1 0 1 0 0 0 0 0 0 0 1
 0 0 0 1 1 0 0 0 0 0 0 1 0 1 1 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0
 1 0 0 0 0 0 0 0 1 1 1 0 1 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 1 0 1 0 0 0 1 0 0
 1 0 0 0 0 0 0 0 0 0 1 0 1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 0 1 1 0 1 0 0 0 1 1
 0 1 0 0 1 1 0 0 0 0 0 0 0 1 0 1 0 0 0 0 1 1 0 0 0 1 0 1 0 0 1 0 1 0 0 1 0
 0 1 1 1 1 0 0 1 0 0 0]

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

Тип этих значений — numpy.ndarray, который мы можем легко преобразовать в серию панд:

predictions = et.predict(imp.transform(test_df[columns].values))
test_df["Survived"] = pd.Series(predictions)

Затем мы можем записать столбцы PassengerId и Survived в файл:

test_df.to_csv("foo.csv", cols=['PassengerId', 'Survived'], index=False)

Then output file looks like this:

$ head -n5 foo.csv 
PassengerId,Survived
892,0
893,1
894,0

The code we’ve written is on github in case it’s useful to anyone.